]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
vmstatus: define return propertries
[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",
b1a70cab 368 properties => $PVE::QemuServer::vmstatus_return_properties,
1e3baf05
DM
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};
a0d1b1a2
DM
385 push @$res, $data;
386 }
1e3baf05 387
a0d1b1a2 388 return $res;
1e3baf05
DM
389 }});
390
d703d4c0 391
d703d4c0 392
1e3baf05 393__PACKAGE__->register_method({
afdb31d5
DM
394 name => 'create_vm',
395 path => '',
1e3baf05 396 method => 'POST',
3e16d5fc 397 description => "Create or restore a virtual machine.",
a0d1b1a2 398 permissions => {
f9bfceef
DM
399 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
400 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
401 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
402 user => 'all', # check inside
a0d1b1a2 403 },
1e3baf05
DM
404 protected => 1,
405 proxyto => 'node',
406 parameters => {
407 additionalProperties => 0,
408 properties => PVE::QemuServer::json_config_properties(
409 {
410 node => get_standard_option('pve-node'),
65e866e5 411 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
3e16d5fc
DM
412 archive => {
413 description => "The backup file.",
414 type => 'string',
415 optional => 1,
416 maxLength => 255,
65e866e5 417 completion => \&PVE::QemuServer::complete_backup_archives,
3e16d5fc
DM
418 },
419 storage => get_standard_option('pve-storage-id', {
420 description => "Default storage.",
421 optional => 1,
335af808 422 completion => \&PVE::QemuServer::complete_storage,
3e16d5fc
DM
423 }),
424 force => {
afdb31d5 425 optional => 1,
3e16d5fc
DM
426 type => 'boolean',
427 description => "Allow to overwrite existing VM.",
51586c3a
DM
428 requires => 'archive',
429 },
430 unique => {
afdb31d5 431 optional => 1,
51586c3a
DM
432 type => 'boolean',
433 description => "Assign a unique random ethernet address.",
434 requires => 'archive',
3e16d5fc 435 },
75466c4f 436 pool => {
a0d1b1a2
DM
437 optional => 1,
438 type => 'string', format => 'pve-poolid',
439 description => "Add the VM to the specified pool.",
440 },
7c536e11 441 bwlimit => {
eb84566b 442 description => "Override i/o bandwidth limit (in KiB/s).",
7c536e11
WB
443 optional => 1,
444 type => 'integer',
445 minimum => '0',
e33f774d
TL
446 },
447 start => {
448 optional => 1,
449 type => 'boolean',
450 default => 0,
451 description => "Start VM after it was created successfully.",
452 },
1e3baf05
DM
453 }),
454 },
afdb31d5 455 returns => {
5fdbe4f0
DM
456 type => 'string',
457 },
1e3baf05
DM
458 code => sub {
459 my ($param) = @_;
460
5fdbe4f0
DM
461 my $rpcenv = PVE::RPCEnvironment::get();
462
a0d1b1a2 463 my $authuser = $rpcenv->get_user();
5fdbe4f0 464
1e3baf05
DM
465 my $node = extract_param($param, 'node');
466
1e3baf05
DM
467 my $vmid = extract_param($param, 'vmid');
468
3e16d5fc 469 my $archive = extract_param($param, 'archive');
8ba8418c 470 my $is_restore = !!$archive;
3e16d5fc
DM
471
472 my $storage = extract_param($param, 'storage');
473
51586c3a
DM
474 my $force = extract_param($param, 'force');
475
476 my $unique = extract_param($param, 'unique');
75466c4f 477
a0d1b1a2 478 my $pool = extract_param($param, 'pool');
51586c3a 479
7c536e11
WB
480 my $bwlimit = extract_param($param, 'bwlimit');
481
e33f774d
TL
482 my $start_after_create = extract_param($param, 'start');
483
ffda963f 484 my $filename = PVE::QemuConfig->config_file($vmid);
afdb31d5
DM
485
486 my $storecfg = PVE::Storage::config();
1e3baf05 487
0c9a7596
AD
488 if (defined(my $ssh_keys = $param->{sshkeys})) {
489 $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
490 PVE::Tools::validate_ssh_public_keys($ssh_keys);
491 }
492
3e16d5fc 493 PVE::Cluster::check_cfs_quorum();
1e3baf05 494
a0d1b1a2
DM
495 if (defined($pool)) {
496 $rpcenv->check_pool_exist($pool);
75466c4f 497 }
a0d1b1a2 498
fcbb753e 499 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
a0d1b1a2
DM
500 if defined($storage);
501
f9bfceef
DM
502 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
503 # OK
504 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
505 # OK
506 } elsif ($archive && $force && (-f $filename) &&
507 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
508 # OK: user has VM.Backup permissions, and want to restore an existing VM
509 } else {
510 raise_perm_exc();
511 }
512
afdb31d5 513 if (!$archive) {
3e16d5fc 514 &$resolve_cdrom_alias($param);
1e3baf05 515
fcbb753e 516 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
ae57f6b3
DM
517
518 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
519
3e16d5fc 520 foreach my $opt (keys %$param) {
74479ee9 521 if (PVE::QemuServer::is_valid_drivename($opt)) {
3e16d5fc
DM
522 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
523 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
afdb31d5 524
3e16d5fc
DM
525 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
526 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
527 }
1e3baf05 528 }
3e16d5fc
DM
529
530 PVE::QemuServer::add_random_macs($param);
51586c3a
DM
531 } else {
532 my $keystr = join(' ', keys %$param);
bc4dcb99
DM
533 raise_param_exc({ archive => "option conflicts with other options ($keystr)"}) if $keystr;
534
5b9d692a 535 if ($archive eq '-') {
afdb31d5 536 die "pipe requires cli environment\n"
d7810bc1 537 if $rpcenv->{type} ne 'cli';
5b9d692a 538 } else {
9bb3acf1 539 PVE::Storage::check_volume_access($rpcenv, $authuser, $storecfg, $vmid, $archive);
c9928b3d 540 $archive = PVE::Storage::abs_filesystem_path($storecfg, $archive);
971f27c4 541 }
1e3baf05
DM
542 }
543
4fedc13b 544 my $emsg = $is_restore ? "unable to restore VM $vmid -" : "unable to create VM $vmid -";
3e16d5fc 545
4fedc13b
TL
546 eval { PVE::QemuConfig->create_and_lock_config($vmid, $force) };
547 die "$emsg $@" if $@;
3e16d5fc 548
4fedc13b
TL
549 my $restorefn = sub {
550 my $conf = PVE::QemuConfig->load_config($vmid);
4d8d55f1 551
4fedc13b 552 PVE::QemuConfig->check_protection($conf, $emsg);
3a07a8a9 553
4fedc13b
TL
554 die "$emsg vm is running\n" if PVE::QemuServer::check_running($vmid);
555 die "$emsg vm is a template\n" if PVE::QemuConfig->is_template($conf);
3e16d5fc
DM
556
557 my $realcmd = sub {
a0d1b1a2 558 PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
51586c3a 559 storage => $storage,
a0d1b1a2 560 pool => $pool,
7c536e11
WB
561 unique => $unique,
562 bwlimit => $bwlimit, });
502d18a2 563
be517049 564 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
e33f774d 565
5bf96183
WB
566 if ($start_after_create) {
567 eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) };
568 warn $@ if $@;
569 }
3e16d5fc
DM
570 };
571
223e032b
WL
572 # ensure no old replication state are exists
573 PVE::ReplicationState::delete_guest_states($vmid);
574
8ba8418c 575 return PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
3e16d5fc 576 };
1e3baf05 577
1e3baf05 578 my $createfn = sub {
223e032b
WL
579 # ensure no old replication state are exists
580 PVE::ReplicationState::delete_guest_states($vmid);
581
5fdbe4f0 582 my $realcmd = sub {
1e3baf05 583
5fdbe4f0 584 my $vollist = [];
1e3baf05 585
1858638f
DM
586 my $conf = $param;
587
5fdbe4f0 588 eval {
ae57f6b3 589
1858638f 590 $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
1e3baf05 591
ae2fcb3b
EK
592 if (!$conf->{bootdisk}) {
593 my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
594 $conf->{bootdisk} = $firstdisk if $firstdisk;
5fdbe4f0 595 }
1e3baf05 596
47314bf5
DM
597 # auto generate uuid if user did not specify smbios1 option
598 if (!$conf->{smbios1}) {
ae2fcb3b 599 $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
47314bf5
DM
600 }
601
ffda963f 602 PVE::QemuConfig->write_config($vmid, $conf);
ae9ca91d 603
5fdbe4f0
DM
604 };
605 my $err = $@;
1e3baf05 606
5fdbe4f0
DM
607 if ($err) {
608 foreach my $volid (@$vollist) {
609 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
610 warn $@ if $@;
611 }
4fedc13b 612 die "$emsg $err";
5fdbe4f0 613 }
502d18a2 614
be517049 615 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
5fdbe4f0
DM
616 };
617
e33f774d
TL
618 PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
619
620 if ($start_after_create) {
621 print "Execute autostart\n";
5bf96183
WB
622 eval { PVE::API2::Qemu->vm_start({vmid => $vmid, node => $node}) };
623 warn $@ if $@;
e33f774d 624 }
5fdbe4f0
DM
625 };
626
5bf96183
WB
627 my ($code, $worker_name);
628 if ($is_restore) {
629 $worker_name = 'qmrestore';
630 $code = sub {
631 eval { $restorefn->() };
632 if (my $err = $@) {
633 eval { PVE::QemuConfig->remove_lock($vmid, 'create') };
634 warn $@ if $@;
635 die $err;
636 }
637 };
638 } else {
639 $worker_name = 'qmcreate';
640 $code = sub {
641 eval { $createfn->() };
642 if (my $err = $@) {
643 eval {
644 my $conffile = PVE::QemuConfig->config_file($vmid);
645 unlink($conffile)
646 or die "failed to remove config file: $@\n";
647 };
648 warn $@ if $@;
649 die $err;
650 }
651 };
652 }
8ba8418c
TL
653
654 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
1e3baf05
DM
655 }});
656
657__PACKAGE__->register_method({
658 name => 'vmdiridx',
afdb31d5 659 path => '{vmid}',
1e3baf05
DM
660 method => 'GET',
661 proxyto => 'node',
662 description => "Directory index",
a0d1b1a2
DM
663 permissions => {
664 user => 'all',
665 },
1e3baf05
DM
666 parameters => {
667 additionalProperties => 0,
668 properties => {
669 node => get_standard_option('pve-node'),
670 vmid => get_standard_option('pve-vmid'),
671 },
672 },
673 returns => {
674 type => 'array',
675 items => {
676 type => "object",
677 properties => {
678 subdir => { type => 'string' },
679 },
680 },
681 links => [ { rel => 'child', href => "{subdir}" } ],
682 },
683 code => sub {
684 my ($param) = @_;
685
686 my $res = [
687 { subdir => 'config' },
df2a2dbb 688 { subdir => 'pending' },
1e3baf05
DM
689 { subdir => 'status' },
690 { subdir => 'unlink' },
691 { subdir => 'vncproxy' },
87302002 692 { subdir => 'termproxy' },
3ea94c60 693 { subdir => 'migrate' },
2f48a4f5 694 { subdir => 'resize' },
586bfa78 695 { subdir => 'move' },
1e3baf05
DM
696 { subdir => 'rrd' },
697 { subdir => 'rrddata' },
91c94f0a 698 { subdir => 'monitor' },
d1a47427 699 { subdir => 'agent' },
7e7d7b61 700 { subdir => 'snapshot' },
288eeea8 701 { subdir => 'spiceproxy' },
7aa608d6 702 { subdir => 'sendkey' },
228a998b 703 { subdir => 'firewall' },
1e3baf05 704 ];
afdb31d5 705
1e3baf05
DM
706 return $res;
707 }});
708
228a998b 709__PACKAGE__->register_method ({
f34ebd52 710 subclass => "PVE::API2::Firewall::VM",
228a998b
DM
711 path => '{vmid}/firewall',
712});
713
b8158701
DC
714__PACKAGE__->register_method ({
715 subclass => "PVE::API2::Qemu::Agent",
716 path => '{vmid}/agent',
717});
718
1e3baf05 719__PACKAGE__->register_method({
afdb31d5
DM
720 name => 'rrd',
721 path => '{vmid}/rrd',
1e3baf05
DM
722 method => 'GET',
723 protected => 1, # fixme: can we avoid that?
724 permissions => {
378b359e 725 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
726 },
727 description => "Read VM RRD statistics (returns PNG)",
728 parameters => {
729 additionalProperties => 0,
730 properties => {
731 node => get_standard_option('pve-node'),
732 vmid => get_standard_option('pve-vmid'),
733 timeframe => {
734 description => "Specify the time frame you are interested in.",
735 type => 'string',
736 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
737 },
738 ds => {
739 description => "The list of datasources you want to display.",
740 type => 'string', format => 'pve-configid-list',
741 },
742 cf => {
743 description => "The RRD consolidation function",
744 type => 'string',
745 enum => [ 'AVERAGE', 'MAX' ],
746 optional => 1,
747 },
748 },
749 },
750 returns => {
751 type => "object",
752 properties => {
753 filename => { type => 'string' },
754 },
755 },
756 code => sub {
757 my ($param) = @_;
758
759 return PVE::Cluster::create_rrd_graph(
afdb31d5 760 "pve2-vm/$param->{vmid}", $param->{timeframe},
1e3baf05 761 $param->{ds}, $param->{cf});
afdb31d5 762
1e3baf05
DM
763 }});
764
765__PACKAGE__->register_method({
afdb31d5
DM
766 name => 'rrddata',
767 path => '{vmid}/rrddata',
1e3baf05
DM
768 method => 'GET',
769 protected => 1, # fixme: can we avoid that?
770 permissions => {
378b359e 771 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1e3baf05
DM
772 },
773 description => "Read VM RRD statistics",
774 parameters => {
775 additionalProperties => 0,
776 properties => {
777 node => get_standard_option('pve-node'),
778 vmid => get_standard_option('pve-vmid'),
779 timeframe => {
780 description => "Specify the time frame you are interested in.",
781 type => 'string',
782 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
783 },
784 cf => {
785 description => "The RRD consolidation function",
786 type => 'string',
787 enum => [ 'AVERAGE', 'MAX' ],
788 optional => 1,
789 },
790 },
791 },
792 returns => {
793 type => "array",
794 items => {
795 type => "object",
796 properties => {},
797 },
798 },
799 code => sub {
800 my ($param) = @_;
801
802 return PVE::Cluster::create_rrd_data(
803 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
804 }});
805
806
807__PACKAGE__->register_method({
afdb31d5
DM
808 name => 'vm_config',
809 path => '{vmid}/config',
1e3baf05
DM
810 method => 'GET',
811 proxyto => 'node',
1e7f2726 812 description => "Get current virtual machine configuration. This does not include pending configuration changes (see 'pending' API).",
a0d1b1a2
DM
813 permissions => {
814 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
815 },
1e3baf05
DM
816 parameters => {
817 additionalProperties => 0,
818 properties => {
819 node => get_standard_option('pve-node'),
335af808 820 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
6d89b548
DM
821 current => {
822 description => "Get current values (instead of pending values).",
823 optional => 1,
824 default => 0,
825 type => 'boolean',
826 },
1e3baf05
DM
827 },
828 },
afdb31d5 829 returns => {
ce9b0a38 830 description => "The current VM configuration.",
1e3baf05 831 type => "object",
ce9b0a38 832 properties => PVE::QemuServer::json_config_properties({
554ac7e7
DM
833 digest => {
834 type => 'string',
835 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
836 }
ce9b0a38 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 },
b1a70cab
DM
1865 returns => {
1866 type => 'object',
1867 properties => {
1868 %$PVE::QemuServer::vmstatus_return_properties,
1869 ha => {
1870 description => "HA manager service status.",
1871 type => 'object',
1872 },
1873 spice => {
1874 description => "Qemu VGA configuration supports spice.",
1875 type => 'boolean',
1876 optional => 1,
1877 },
1878 agent => {
1879 description => "Qemu GuestAgent enabled in config.",
1880 type => 'boolean',
1881 optional => 1,
1882 },
1883 },
1884 },
1e3baf05
DM
1885 code => sub {
1886 my ($param) = @_;
1887
1888 # test if VM exists
ffda963f 1889 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 1890
03a33f30 1891 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1892 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1893
4d2a734e 1894 $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
8610701a 1895
86b8228b 1896 $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
46246f04 1897
c9a074b8
DC
1898 $status->{agent} = 1 if $conf->{agent};
1899
8610701a 1900 return $status;
1e3baf05
DM
1901 }});
1902
1903__PACKAGE__->register_method({
afdb31d5 1904 name => 'vm_start',
5fdbe4f0
DM
1905 path => '{vmid}/status/start',
1906 method => 'POST',
1e3baf05
DM
1907 protected => 1,
1908 proxyto => 'node',
5fdbe4f0 1909 description => "Start virtual machine.",
a0d1b1a2
DM
1910 permissions => {
1911 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1912 },
1e3baf05
DM
1913 parameters => {
1914 additionalProperties => 0,
1915 properties => {
1916 node => get_standard_option('pve-node'),
ab5904f7
TL
1917 vmid => get_standard_option('pve-vmid',
1918 { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60
DM
1919 skiplock => get_standard_option('skiplock'),
1920 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1921 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
2de2d6f7
TL
1922 migration_type => {
1923 type => 'string',
1924 enum => ['secure', 'insecure'],
1925 description => "Migration traffic is encrypted using an SSH " .
1926 "tunnel by default. On secure, completely private networks " .
1927 "this can be disabled to increase performance.",
1928 optional => 1,
1929 },
1930 migration_network => {
29ddbe70 1931 type => 'string', format => 'CIDR',
2de2d6f7
TL
1932 description => "CIDR of the (sub) network that is used for migration.",
1933 optional => 1,
1934 },
952958bc 1935 machine => get_standard_option('pve-qm-machine'),
2189246c 1936 targetstorage => {
bd2d5fe6 1937 description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2189246c
AD
1938 type => 'string',
1939 optional => 1
1940 }
1e3baf05
DM
1941 },
1942 },
afdb31d5 1943 returns => {
5fdbe4f0
DM
1944 type => 'string',
1945 },
1e3baf05
DM
1946 code => sub {
1947 my ($param) = @_;
1948
1949 my $rpcenv = PVE::RPCEnvironment::get();
1950
a0d1b1a2 1951 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1952
1953 my $node = extract_param($param, 'node');
1954
1e3baf05
DM
1955 my $vmid = extract_param($param, 'vmid');
1956
952958bc
DM
1957 my $machine = extract_param($param, 'machine');
1958
3ea94c60 1959 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1960 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1961 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1962
1e3baf05 1963 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1964 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1965 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1966
7e8dcf2c
AD
1967 my $migratedfrom = extract_param($param, 'migratedfrom');
1968 raise_param_exc({ migratedfrom => "Only root may use this option." })
1969 if $migratedfrom && $authuser ne 'root@pam';
1970
2de2d6f7
TL
1971 my $migration_type = extract_param($param, 'migration_type');
1972 raise_param_exc({ migration_type => "Only root may use this option." })
1973 if $migration_type && $authuser ne 'root@pam';
1974
1975 my $migration_network = extract_param($param, 'migration_network');
1976 raise_param_exc({ migration_network => "Only root may use this option." })
1977 if $migration_network && $authuser ne 'root@pam';
1978
2189246c
AD
1979 my $targetstorage = extract_param($param, 'targetstorage');
1980 raise_param_exc({ targetstorage => "Only root may use this option." })
1981 if $targetstorage && $authuser ne 'root@pam';
1982
1983 raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
1984 if $targetstorage && !$migratedfrom;
1985
7c14dcae
DM
1986 # read spice ticket from STDIN
1987 my $spice_ticket;
1988 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
e5caa02e 1989 if (defined(my $line = <STDIN>)) {
760fb3c8
DM
1990 chomp $line;
1991 $spice_ticket = $line;
1992 }
7c14dcae
DM
1993 }
1994
98cbd0f4
WB
1995 PVE::Cluster::check_cfs_quorum();
1996
afdb31d5 1997 my $storecfg = PVE::Storage::config();
5fdbe4f0 1998
2003f0f8 1999 if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri &&
cce37749 2000 $rpcenv->{type} ne 'ha') {
5fdbe4f0 2001
88fc87b4
DM
2002 my $hacmd = sub {
2003 my $upid = shift;
5fdbe4f0 2004
c44291cd 2005 my $service = "vm:$vmid";
5fdbe4f0 2006
2a7e2b82 2007 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
88fc87b4 2008
02765844 2009 print "Requesting HA start for VM $vmid\n";
88fc87b4
DM
2010
2011 PVE::Tools::run_command($cmd);
2012
2013 return;
2014 };
2015
2016 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2017
2018 } else {
2019
2020 my $realcmd = sub {
2021 my $upid = shift;
2022
2023 syslog('info', "start VM $vmid: $upid\n");
2024
fa8ea931 2025 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2189246c 2026 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
88fc87b4
DM
2027
2028 return;
2029 };
5fdbe4f0 2030
88fc87b4
DM
2031 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2032 }
5fdbe4f0
DM
2033 }});
2034
2035__PACKAGE__->register_method({
afdb31d5 2036 name => 'vm_stop',
5fdbe4f0
DM
2037 path => '{vmid}/status/stop',
2038 method => 'POST',
2039 protected => 1,
2040 proxyto => 'node',
346130b2 2041 description => "Stop virtual machine. The qemu process will exit immediately. This" .
d6c747ff 2042 "is akin to pulling the power plug of a running computer and may damage the VM data",
a0d1b1a2
DM
2043 permissions => {
2044 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2045 },
5fdbe4f0
DM
2046 parameters => {
2047 additionalProperties => 0,
2048 properties => {
2049 node => get_standard_option('pve-node'),
ab5904f7
TL
2050 vmid => get_standard_option('pve-vmid',
2051 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2052 skiplock => get_standard_option('skiplock'),
debe8882 2053 migratedfrom => get_standard_option('pve-node', { optional => 1 }),
c6bb9502
DM
2054 timeout => {
2055 description => "Wait maximal timeout seconds.",
2056 type => 'integer',
2057 minimum => 0,
2058 optional => 1,
254575e9
DM
2059 },
2060 keepActive => {
94a17e1d 2061 description => "Do not deactivate storage volumes.",
254575e9
DM
2062 type => 'boolean',
2063 optional => 1,
2064 default => 0,
c6bb9502 2065 }
5fdbe4f0
DM
2066 },
2067 },
afdb31d5 2068 returns => {
5fdbe4f0
DM
2069 type => 'string',
2070 },
2071 code => sub {
2072 my ($param) = @_;
2073
2074 my $rpcenv = PVE::RPCEnvironment::get();
2075
a0d1b1a2 2076 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2077
2078 my $node = extract_param($param, 'node');
2079
2080 my $vmid = extract_param($param, 'vmid');
2081
2082 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2083 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2084 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2085
254575e9 2086 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2087 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2088 if $keepActive && $authuser ne 'root@pam';
254575e9 2089
af30308f
DM
2090 my $migratedfrom = extract_param($param, 'migratedfrom');
2091 raise_param_exc({ migratedfrom => "Only root may use this option." })
2092 if $migratedfrom && $authuser ne 'root@pam';
2093
2094
ff1a2432
DM
2095 my $storecfg = PVE::Storage::config();
2096
2003f0f8 2097 if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
5fdbe4f0 2098
88fc87b4
DM
2099 my $hacmd = sub {
2100 my $upid = shift;
5fdbe4f0 2101
c44291cd 2102 my $service = "vm:$vmid";
c6bb9502 2103
e0feef86 2104 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
88fc87b4 2105
02765844 2106 print "Requesting HA stop for VM $vmid\n";
88fc87b4
DM
2107
2108 PVE::Tools::run_command($cmd);
5fdbe4f0 2109
88fc87b4
DM
2110 return;
2111 };
2112
2113 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2114
2115 } else {
2116 my $realcmd = sub {
2117 my $upid = shift;
2118
2119 syslog('info', "stop VM $vmid: $upid\n");
2120
2121 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 2122 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
2123
2124 return;
2125 };
2126
2127 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2128 }
5fdbe4f0
DM
2129 }});
2130
2131__PACKAGE__->register_method({
afdb31d5 2132 name => 'vm_reset',
5fdbe4f0
DM
2133 path => '{vmid}/status/reset',
2134 method => 'POST',
2135 protected => 1,
2136 proxyto => 'node',
2137 description => "Reset virtual machine.",
a0d1b1a2
DM
2138 permissions => {
2139 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2140 },
5fdbe4f0
DM
2141 parameters => {
2142 additionalProperties => 0,
2143 properties => {
2144 node => get_standard_option('pve-node'),
ab5904f7
TL
2145 vmid => get_standard_option('pve-vmid',
2146 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2147 skiplock => get_standard_option('skiplock'),
2148 },
2149 },
afdb31d5 2150 returns => {
5fdbe4f0
DM
2151 type => 'string',
2152 },
2153 code => sub {
2154 my ($param) = @_;
2155
2156 my $rpcenv = PVE::RPCEnvironment::get();
2157
a0d1b1a2 2158 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2159
2160 my $node = extract_param($param, 'node');
2161
2162 my $vmid = extract_param($param, 'vmid');
2163
2164 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2165 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2166 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2167
ff1a2432
DM
2168 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2169
5fdbe4f0
DM
2170 my $realcmd = sub {
2171 my $upid = shift;
2172
1e3baf05 2173 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
2174
2175 return;
2176 };
2177
a0d1b1a2 2178 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2179 }});
2180
2181__PACKAGE__->register_method({
afdb31d5 2182 name => 'vm_shutdown',
5fdbe4f0
DM
2183 path => '{vmid}/status/shutdown',
2184 method => 'POST',
2185 protected => 1,
2186 proxyto => 'node',
d6c747ff
EK
2187 description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2188 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
a0d1b1a2
DM
2189 permissions => {
2190 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2191 },
5fdbe4f0
DM
2192 parameters => {
2193 additionalProperties => 0,
2194 properties => {
2195 node => get_standard_option('pve-node'),
ab5904f7
TL
2196 vmid => get_standard_option('pve-vmid',
2197 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2198 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
2199 timeout => {
2200 description => "Wait maximal timeout seconds.",
2201 type => 'integer',
2202 minimum => 0,
2203 optional => 1,
9269013a
DM
2204 },
2205 forceStop => {
2206 description => "Make sure the VM stops.",
2207 type => 'boolean',
2208 optional => 1,
2209 default => 0,
254575e9
DM
2210 },
2211 keepActive => {
94a17e1d 2212 description => "Do not deactivate storage volumes.",
254575e9
DM
2213 type => 'boolean',
2214 optional => 1,
2215 default => 0,
c6bb9502 2216 }
5fdbe4f0
DM
2217 },
2218 },
afdb31d5 2219 returns => {
5fdbe4f0
DM
2220 type => 'string',
2221 },
2222 code => sub {
2223 my ($param) = @_;
2224
2225 my $rpcenv = PVE::RPCEnvironment::get();
2226
a0d1b1a2 2227 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2228
2229 my $node = extract_param($param, 'node');
2230
2231 my $vmid = extract_param($param, 'vmid');
2232
2233 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2234 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2235 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2236
254575e9 2237 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2238 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2239 if $keepActive && $authuser ne 'root@pam';
254575e9 2240
02d07cf5
DM
2241 my $storecfg = PVE::Storage::config();
2242
89897367
DC
2243 my $shutdown = 1;
2244
2245 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2246 # otherwise, we will infer a shutdown command, but run into the timeout,
2247 # then when the vm is resumed, it will instantly shutdown
2248 #
2249 # checking the qmp status here to get feedback to the gui/cli/api
2250 # and the status query should not take too long
2251 my $qmpstatus;
2252 eval {
2253 $qmpstatus = PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
2254 };
2255 my $err = $@ if $@;
2256
2257 if (!$err && $qmpstatus->{status} eq "paused") {
2258 if ($param->{forceStop}) {
2259 warn "VM is paused - stop instead of shutdown\n";
2260 $shutdown = 0;
2261 } else {
2262 die "VM is paused - cannot shutdown\n";
2263 }
2264 }
2265
ae849692
DM
2266 if (PVE::HA::Config::vm_is_ha_managed($vmid) &&
2267 ($rpcenv->{type} ne 'ha')) {
5fdbe4f0 2268
ae849692
DM
2269 my $hacmd = sub {
2270 my $upid = shift;
5fdbe4f0 2271
ae849692 2272 my $service = "vm:$vmid";
c6bb9502 2273
ae849692
DM
2274 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2275
02765844 2276 print "Requesting HA stop for VM $vmid\n";
ae849692
DM
2277
2278 PVE::Tools::run_command($cmd);
2279
2280 return;
2281 };
2282
2283 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2284
2285 } else {
2286
2287 my $realcmd = sub {
2288 my $upid = shift;
2289
2290 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2291
ae849692
DM
2292 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2293 $shutdown, $param->{forceStop}, $keepActive);
2294
2295 return;
2296 };
2297
2298 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2299 }
5fdbe4f0
DM
2300 }});
2301
2302__PACKAGE__->register_method({
afdb31d5 2303 name => 'vm_suspend',
5fdbe4f0
DM
2304 path => '{vmid}/status/suspend',
2305 method => 'POST',
2306 protected => 1,
2307 proxyto => 'node',
2308 description => "Suspend virtual machine.",
a0d1b1a2
DM
2309 permissions => {
2310 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2311 },
5fdbe4f0
DM
2312 parameters => {
2313 additionalProperties => 0,
2314 properties => {
2315 node => get_standard_option('pve-node'),
ab5904f7
TL
2316 vmid => get_standard_option('pve-vmid',
2317 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2318 skiplock => get_standard_option('skiplock'),
2319 },
2320 },
afdb31d5 2321 returns => {
5fdbe4f0
DM
2322 type => 'string',
2323 },
2324 code => sub {
2325 my ($param) = @_;
2326
2327 my $rpcenv = PVE::RPCEnvironment::get();
2328
a0d1b1a2 2329 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2330
2331 my $node = extract_param($param, 'node');
2332
2333 my $vmid = extract_param($param, 'vmid');
2334
2335 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2336 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2337 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2338
ff1a2432
DM
2339 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2340
5fdbe4f0
DM
2341 my $realcmd = sub {
2342 my $upid = shift;
2343
2344 syslog('info', "suspend VM $vmid: $upid\n");
2345
1e3baf05 2346 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
2347
2348 return;
2349 };
2350
a0d1b1a2 2351 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2352 }});
2353
2354__PACKAGE__->register_method({
afdb31d5 2355 name => 'vm_resume',
5fdbe4f0
DM
2356 path => '{vmid}/status/resume',
2357 method => 'POST',
2358 protected => 1,
2359 proxyto => 'node',
2360 description => "Resume virtual machine.",
a0d1b1a2
DM
2361 permissions => {
2362 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2363 },
5fdbe4f0
DM
2364 parameters => {
2365 additionalProperties => 0,
2366 properties => {
2367 node => get_standard_option('pve-node'),
ab5904f7
TL
2368 vmid => get_standard_option('pve-vmid',
2369 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2370 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2371 nocheck => { type => 'boolean', optional => 1 },
2372
5fdbe4f0
DM
2373 },
2374 },
afdb31d5 2375 returns => {
5fdbe4f0
DM
2376 type => 'string',
2377 },
2378 code => sub {
2379 my ($param) = @_;
2380
2381 my $rpcenv = PVE::RPCEnvironment::get();
2382
a0d1b1a2 2383 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2384
2385 my $node = extract_param($param, 'node');
2386
2387 my $vmid = extract_param($param, 'vmid');
2388
2389 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2390 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2391 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2392
289e0b85
AD
2393 my $nocheck = extract_param($param, 'nocheck');
2394
2395 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2396
5fdbe4f0
DM
2397 my $realcmd = sub {
2398 my $upid = shift;
2399
2400 syslog('info', "resume VM $vmid: $upid\n");
2401
289e0b85 2402 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
1e3baf05 2403
5fdbe4f0
DM
2404 return;
2405 };
2406
a0d1b1a2 2407 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2408 }});
2409
2410__PACKAGE__->register_method({
afdb31d5 2411 name => 'vm_sendkey',
5fdbe4f0
DM
2412 path => '{vmid}/sendkey',
2413 method => 'PUT',
2414 protected => 1,
2415 proxyto => 'node',
2416 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2417 permissions => {
2418 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2419 },
5fdbe4f0
DM
2420 parameters => {
2421 additionalProperties => 0,
2422 properties => {
2423 node => get_standard_option('pve-node'),
ab5904f7
TL
2424 vmid => get_standard_option('pve-vmid',
2425 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2426 skiplock => get_standard_option('skiplock'),
2427 key => {
2428 description => "The key (qemu monitor encoding).",
2429 type => 'string'
2430 }
2431 },
2432 },
2433 returns => { type => 'null'},
2434 code => sub {
2435 my ($param) = @_;
2436
2437 my $rpcenv = PVE::RPCEnvironment::get();
2438
a0d1b1a2 2439 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2440
2441 my $node = extract_param($param, 'node');
2442
2443 my $vmid = extract_param($param, 'vmid');
2444
2445 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2446 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2447 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2448
2449 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2450
2451 return;
1e3baf05
DM
2452 }});
2453
1ac0d2ee
AD
2454__PACKAGE__->register_method({
2455 name => 'vm_feature',
2456 path => '{vmid}/feature',
2457 method => 'GET',
2458 proxyto => 'node',
75466c4f 2459 protected => 1,
1ac0d2ee
AD
2460 description => "Check if feature for virtual machine is available.",
2461 permissions => {
2462 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2463 },
2464 parameters => {
2465 additionalProperties => 0,
2466 properties => {
2467 node => get_standard_option('pve-node'),
2468 vmid => get_standard_option('pve-vmid'),
2469 feature => {
2470 description => "Feature to check.",
2471 type => 'string',
7758ce86 2472 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2473 },
2474 snapname => get_standard_option('pve-snapshot-name', {
2475 optional => 1,
2476 }),
2477 },
1ac0d2ee
AD
2478 },
2479 returns => {
719893a9
DM
2480 type => "object",
2481 properties => {
2482 hasFeature => { type => 'boolean' },
7043d946 2483 nodes => {
719893a9
DM
2484 type => 'array',
2485 items => { type => 'string' },
2486 }
2487 },
1ac0d2ee
AD
2488 },
2489 code => sub {
2490 my ($param) = @_;
2491
2492 my $node = extract_param($param, 'node');
2493
2494 my $vmid = extract_param($param, 'vmid');
2495
2496 my $snapname = extract_param($param, 'snapname');
2497
2498 my $feature = extract_param($param, 'feature');
2499
2500 my $running = PVE::QemuServer::check_running($vmid);
2501
ffda963f 2502 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2503
2504 if($snapname){
2505 my $snap = $conf->{snapshots}->{$snapname};
2506 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2507 $conf = $snap;
2508 }
2509 my $storecfg = PVE::Storage::config();
2510
719893a9 2511 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2512 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2513
719893a9
DM
2514 return {
2515 hasFeature => $hasFeature,
2516 nodes => [ keys %$nodelist ],
7043d946 2517 };
1ac0d2ee
AD
2518 }});
2519
6116f729 2520__PACKAGE__->register_method({
9418baad
DM
2521 name => 'clone_vm',
2522 path => '{vmid}/clone',
6116f729
DM
2523 method => 'POST',
2524 protected => 1,
2525 proxyto => 'node',
37329185 2526 description => "Create a copy of virtual machine/template.",
6116f729 2527 permissions => {
9418baad 2528 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2529 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2530 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2531 check =>
2532 [ 'and',
9418baad 2533 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2534 [ 'or',
6116f729
DM
2535 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2536 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2537 ],
2538 ]
2539 },
2540 parameters => {
2541 additionalProperties => 0,
2542 properties => {
6116f729 2543 node => get_standard_option('pve-node'),
335af808 2544 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1ae43f8c
DM
2545 newid => get_standard_option('pve-vmid', {
2546 completion => \&PVE::Cluster::complete_next_vmid,
2547 description => 'VMID for the clone.' }),
a60ab1a6
DM
2548 name => {
2549 optional => 1,
2550 type => 'string', format => 'dns-name',
2551 description => "Set a name for the new VM.",
2552 },
2553 description => {
2554 optional => 1,
2555 type => 'string',
2556 description => "Description for the new VM.",
2557 },
75466c4f 2558 pool => {
6116f729
DM
2559 optional => 1,
2560 type => 'string', format => 'pve-poolid',
2561 description => "Add the new VM to the specified pool.",
2562 },
9076d880 2563 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2564 optional => 1,
2565 }),
81f043eb 2566 storage => get_standard_option('pve-storage-id', {
9418baad 2567 description => "Target storage for full clone.",
81f043eb
AD
2568 optional => 1,
2569 }),
55173c6b 2570 'format' => {
fd13b1d0 2571 description => "Target format for file storage. Only valid for full clone.",
42a19c87
AD
2572 type => 'string',
2573 optional => 1,
55173c6b 2574 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2575 },
6116f729
DM
2576 full => {
2577 optional => 1,
55173c6b 2578 type => 'boolean',
fd13b1d0 2579 description => "Create a full copy of all disks. This is always done when " .
9418baad 2580 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729 2581 },
75466c4f 2582 target => get_standard_option('pve-node', {
55173c6b
DM
2583 description => "Target node. Only allowed if the original VM is on shared storage.",
2584 optional => 1,
2585 }),
2586 },
6116f729
DM
2587 },
2588 returns => {
2589 type => 'string',
2590 },
2591 code => sub {
2592 my ($param) = @_;
2593
2594 my $rpcenv = PVE::RPCEnvironment::get();
2595
55173c6b 2596 my $authuser = $rpcenv->get_user();
6116f729
DM
2597
2598 my $node = extract_param($param, 'node');
2599
2600 my $vmid = extract_param($param, 'vmid');
2601
2602 my $newid = extract_param($param, 'newid');
2603
6116f729
DM
2604 my $pool = extract_param($param, 'pool');
2605
2606 if (defined($pool)) {
2607 $rpcenv->check_pool_exist($pool);
2608 }
2609
55173c6b 2610 my $snapname = extract_param($param, 'snapname');
9076d880 2611
81f043eb
AD
2612 my $storage = extract_param($param, 'storage');
2613
42a19c87
AD
2614 my $format = extract_param($param, 'format');
2615
55173c6b
DM
2616 my $target = extract_param($param, 'target');
2617
2618 my $localnode = PVE::INotify::nodename();
2619
751cc556 2620 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2621
2622 PVE::Cluster::check_node_exists($target) if $target;
2623
6116f729
DM
2624 my $storecfg = PVE::Storage::config();
2625
4a5a2590
DM
2626 if ($storage) {
2627 # check if storage is enabled on local node
2628 PVE::Storage::storage_check_enabled($storecfg, $storage);
2629 if ($target) {
2630 # check if storage is available on target node
2631 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2632 # clone only works if target storage is shared
2633 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2634 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2635 }
2636 }
2637
55173c6b 2638 PVE::Cluster::check_cfs_quorum();
6116f729 2639
4e4f83fe
DM
2640 my $running = PVE::QemuServer::check_running($vmid) || 0;
2641
4e4f83fe
DM
2642 # exclusive lock if VM is running - else shared lock is enough;
2643 my $shared_lock = $running ? 0 : 1;
2644
9418baad 2645 my $clonefn = sub {
6116f729 2646
829967a9
DM
2647 # do all tests after lock
2648 # we also try to do all tests before we fork the worker
2649
ffda963f 2650 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2651
ffda963f 2652 PVE::QemuConfig->check_lock($conf);
6116f729 2653
4e4f83fe 2654 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2655
4e4f83fe 2656 die "unexpected state change\n" if $verify_running != $running;
6116f729 2657
75466c4f
DM
2658 die "snapshot '$snapname' does not exist\n"
2659 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2660
fd13b1d0
DM
2661 my $full = extract_param($param, 'full');
2662 if (!defined($full)) {
2663 $full = !PVE::QemuConfig->is_template($conf);
2664 }
2665
2666 die "parameter 'storage' not allowed for linked clones\n"
2667 if defined($storage) && !$full;
2668
2669 die "parameter 'format' not allowed for linked clones\n"
2670 if defined($format) && !$full;
2671
75466c4f 2672 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2673
9418baad 2674 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2675
9418baad 2676 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2677
ffda963f 2678 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2679
2680 die "unable to create VM $newid: config file already exists\n"
2681 if -f $conffile;
2682
9418baad 2683 my $newconf = { lock => 'clone' };
829967a9 2684 my $drives = {};
34456bf0 2685 my $fullclone = {};
829967a9
DM
2686 my $vollist = [];
2687
2688 foreach my $opt (keys %$oldconf) {
2689 my $value = $oldconf->{$opt};
2690
2691 # do not copy snapshot related info
2692 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2693 $opt eq 'vmstate' || $opt eq 'snapstate';
2694
a78ea5df
WL
2695 # no need to copy unused images, because VMID(owner) changes anyways
2696 next if $opt =~ m/^unused\d+$/;
2697
829967a9
DM
2698 # always change MAC! address
2699 if ($opt =~ m/^net(\d+)$/) {
2700 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2701 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2702 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2703 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2704 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2705 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2706 die "unable to parse drive options for '$opt'\n" if !$drive;
931432bd 2707 if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {
829967a9
DM
2708 $newconf->{$opt} = $value; # simply copy configuration
2709 } else {
fd13b1d0 2710 if ($full || PVE::QemuServer::drive_is_cloudinit($drive)) {
6318daca 2711 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2712 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2713 $fullclone->{$opt} = 1;
64ff6fe4
SP
2714 } else {
2715 # not full means clone instead of copy
6318daca 2716 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2717 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2718 }
829967a9
DM
2719 $drives->{$opt} = $drive;
2720 push @$vollist, $drive->{file};
2721 }
2722 } else {
2723 # copy everything else
2724 $newconf->{$opt} = $value;
2725 }
2726 }
2727
cd11416f
DM
2728 # auto generate a new uuid
2729 my ($uuid, $uuid_str);
2730 UUID::generate($uuid);
2731 UUID::unparse($uuid, $uuid_str);
2732 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
f34ebd52 2733 $smbios1->{uuid} = $uuid_str;
cd11416f
DM
2734 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2735
829967a9
DM
2736 delete $newconf->{template};
2737
2738 if ($param->{name}) {
2739 $newconf->{name} = $param->{name};
2740 } else {
c55fee03
DM
2741 if ($oldconf->{name}) {
2742 $newconf->{name} = "Copy-of-$oldconf->{name}";
2743 } else {
2744 $newconf->{name} = "Copy-of-VM-$vmid";
2745 }
829967a9 2746 }
2dd53043 2747
829967a9
DM
2748 if ($param->{description}) {
2749 $newconf->{description} = $param->{description};
2750 }
2751
6116f729 2752 # create empty/temp config - this fails if VM already exists on other node
9418baad 2753 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2754
2755 my $realcmd = sub {
2756 my $upid = shift;
2757
b83e0181 2758 my $newvollist = [];
c6fdd002 2759 my $jobs = {};
6116f729 2760
b83e0181 2761 eval {
eaae66be
TL
2762 local $SIG{INT} =
2763 local $SIG{TERM} =
2764 local $SIG{QUIT} =
2765 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2766
eb15b9f0 2767 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2768
c6fdd002
AD
2769 my $total_jobs = scalar(keys %{$drives});
2770 my $i = 1;
c6fdd002 2771
829967a9
DM
2772 foreach my $opt (keys %$drives) {
2773 my $drive = $drives->{$opt};
3b4cf0f0 2774 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2775
152fe752 2776 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0
WB
2777 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2778 $jobs, $skipcomplete, $oldconf->{agent});
00b095ca 2779
152fe752 2780 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2781
ffda963f 2782 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2783 $i++;
829967a9 2784 }
b83e0181
DM
2785
2786 delete $newconf->{lock};
68e46b84
DC
2787
2788 # do not write pending changes
2789 if ($newconf->{pending}) {
2790 warn "found pending changes, discarding for clone\n";
2791 delete $newconf->{pending};
2792 }
2793
ffda963f 2794 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2795
2796 if ($target) {
baca276d 2797 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2798 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2799 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2800
ffda963f 2801 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2802 die "Failed to move config to node '$target' - rename failed: $!\n"
2803 if !rename($conffile, $newconffile);
2804 }
d703d4c0 2805
be517049 2806 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2807 };
75466c4f 2808 if (my $err = $@) {
6116f729
DM
2809 unlink $conffile;
2810
c6fdd002
AD
2811 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2812
b83e0181
DM
2813 sleep 1; # some storage like rbd need to wait before release volume - really?
2814
2815 foreach my $volid (@$newvollist) {
2816 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2817 warn $@ if $@;
2818 }
9418baad 2819 die "clone failed: $err";
6116f729
DM
2820 }
2821
2822 return;
2823 };
2824
457010cc
AG
2825 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2826
9418baad 2827 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2828 };
2829
ffda963f 2830 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2831 # Aquire exclusive lock lock for $newid
ffda963f 2832 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2833 });
2834
2835 }});
2836
586bfa78 2837__PACKAGE__->register_method({
43bc02a9
DM
2838 name => 'move_vm_disk',
2839 path => '{vmid}/move_disk',
e2cd75fa 2840 method => 'POST',
586bfa78
AD
2841 protected => 1,
2842 proxyto => 'node',
2843 description => "Move volume to different storage.",
2844 permissions => {
c07a9e3d
DM
2845 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2846 check => [ 'and',
2847 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2848 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2849 ],
586bfa78
AD
2850 },
2851 parameters => {
2852 additionalProperties => 0,
c07a9e3d 2853 properties => {
586bfa78 2854 node => get_standard_option('pve-node'),
335af808 2855 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2856 disk => {
2857 type => 'string',
2858 description => "The disk you want to move.",
74479ee9 2859 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2860 },
335af808
DM
2861 storage => get_standard_option('pve-storage-id', {
2862 description => "Target storage.",
2863 completion => \&PVE::QemuServer::complete_storage,
2864 }),
635c3c44 2865 'format' => {
586bfa78
AD
2866 type => 'string',
2867 description => "Target Format.",
2868 enum => [ 'raw', 'qcow2', 'vmdk' ],
2869 optional => 1,
2870 },
70d45e33
DM
2871 delete => {
2872 type => 'boolean',
2873 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2874 optional => 1,
2875 default => 0,
2876 },
586bfa78
AD
2877 digest => {
2878 type => 'string',
2879 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2880 maxLength => 40,
2881 optional => 1,
2882 },
2883 },
2884 },
e2cd75fa
DM
2885 returns => {
2886 type => 'string',
2887 description => "the task ID.",
2888 },
586bfa78
AD
2889 code => sub {
2890 my ($param) = @_;
2891
2892 my $rpcenv = PVE::RPCEnvironment::get();
2893
2894 my $authuser = $rpcenv->get_user();
2895
2896 my $node = extract_param($param, 'node');
2897
2898 my $vmid = extract_param($param, 'vmid');
2899
2900 my $digest = extract_param($param, 'digest');
2901
2902 my $disk = extract_param($param, 'disk');
2903
2904 my $storeid = extract_param($param, 'storage');
2905
2906 my $format = extract_param($param, 'format');
2907
586bfa78
AD
2908 my $storecfg = PVE::Storage::config();
2909
2910 my $updatefn = sub {
2911
ffda963f 2912 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 2913
dcce9b46
FG
2914 PVE::QemuConfig->check_lock($conf);
2915
586bfa78
AD
2916 die "checksum missmatch (file change by other user?)\n"
2917 if $digest && $digest ne $conf->{digest};
586bfa78
AD
2918
2919 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2920
2921 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2922
70d45e33 2923 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78 2924
931432bd 2925 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1);
586bfa78 2926
e2cd75fa 2927 my $oldfmt;
70d45e33 2928 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
2929 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2930 $oldfmt = $1;
2931 }
2932
7043d946 2933 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 2934 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 2935
9dbf9b54
FG
2936 # this only checks snapshots because $disk is passed!
2937 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
2938 die "you can't move a disk with snapshots and delete the source\n"
2939 if $snapshotted && $param->{delete};
2940
586bfa78
AD
2941 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2942
2943 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
2944
2945 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
2946
586bfa78
AD
2947 my $realcmd = sub {
2948
2949 my $newvollist = [];
2950
2951 eval {
6cb0144a
EK
2952 local $SIG{INT} =
2953 local $SIG{TERM} =
2954 local $SIG{QUIT} =
2955 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
586bfa78 2956
9dbf9b54
FG
2957 warn "moving disk with snapshots, snapshots will not be moved!\n"
2958 if $snapshotted;
2959
e2cd75fa
DM
2960 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2961 $vmid, $storeid, $format, 1, $newvollist);
2962
2963 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2964
8793d495 2965 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2966
fbd7dcce
FG
2967 # convert moved disk to base if part of template
2968 PVE::QemuServer::template_create($vmid, $conf, $disk)
2969 if PVE::QemuConfig->is_template($conf);
2970
ffda963f 2971 PVE::QemuConfig->write_config($vmid, $conf);
73272365 2972
f34ebd52 2973 eval {
73272365 2974 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 2975 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
2976 if !$running;
2977 };
2978 warn $@ if $@;
586bfa78
AD
2979 };
2980 if (my $err = $@) {
2981
2982 foreach my $volid (@$newvollist) {
2983 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2984 warn $@ if $@;
2985 }
2986 die "storage migration failed: $err";
2987 }
70d45e33
DM
2988
2989 if ($param->{delete}) {
a3d0bafb
FG
2990 eval {
2991 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
2992 PVE::Storage::vdisk_free($storecfg, $old_volid);
2993 };
2994 warn $@ if $@;
70d45e33 2995 }
586bfa78
AD
2996 };
2997
2998 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2999 };
e2cd75fa 3000
ffda963f 3001 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
3002 }});
3003
3ea94c60 3004__PACKAGE__->register_method({
afdb31d5 3005 name => 'migrate_vm',
3ea94c60
DM
3006 path => '{vmid}/migrate',
3007 method => 'POST',
3008 protected => 1,
3009 proxyto => 'node',
3010 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
3011 permissions => {
3012 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3013 },
3ea94c60
DM
3014 parameters => {
3015 additionalProperties => 0,
3016 properties => {
3017 node => get_standard_option('pve-node'),
335af808
DM
3018 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
3019 target => get_standard_option('pve-node', {
3020 description => "Target node.",
3021 completion => \&PVE::Cluster::complete_migration_target,
3022 }),
3ea94c60
DM
3023 online => {
3024 type => 'boolean',
3025 description => "Use online/live migration.",
3026 optional => 1,
3027 },
3028 force => {
3029 type => 'boolean',
3030 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
3031 optional => 1,
3032 },
2de2d6f7
TL
3033 migration_type => {
3034 type => 'string',
3035 enum => ['secure', 'insecure'],
c07a9e3d 3036 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
3037 optional => 1,
3038 },
3039 migration_network => {
c07a9e3d 3040 type => 'string', format => 'CIDR',
2de2d6f7
TL
3041 description => "CIDR of the (sub) network that is used for migration.",
3042 optional => 1,
3043 },
56af7146
AD
3044 "with-local-disks" => {
3045 type => 'boolean',
3046 description => "Enable live storage migration for local disk",
b74cad8a 3047 optional => 1,
56af7146
AD
3048 },
3049 targetstorage => get_standard_option('pve-storage-id', {
3050 description => "Default target storage.",
3051 optional => 1,
3052 completion => \&PVE::QemuServer::complete_storage,
3053 }),
3ea94c60
DM
3054 },
3055 },
afdb31d5 3056 returns => {
3ea94c60
DM
3057 type => 'string',
3058 description => "the task ID.",
3059 },
3060 code => sub {
3061 my ($param) = @_;
3062
3063 my $rpcenv = PVE::RPCEnvironment::get();
3064
a0d1b1a2 3065 my $authuser = $rpcenv->get_user();
3ea94c60
DM
3066
3067 my $target = extract_param($param, 'target');
3068
3069 my $localnode = PVE::INotify::nodename();
3070 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
3071
3072 PVE::Cluster::check_cfs_quorum();
3073
3074 PVE::Cluster::check_node_exists($target);
3075
3076 my $targetip = PVE::Cluster::remote_node_ip($target);
3077
3078 my $vmid = extract_param($param, 'vmid');
3079
bd2d5fe6 3080 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
3081 if !$param->{online} && $param->{targetstorage};
3082
afdb31d5 3083 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 3084 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 3085
2de2d6f7
TL
3086 raise_param_exc({ migration_type => "Only root may use this option." })
3087 if $param->{migration_type} && $authuser ne 'root@pam';
3088
3089 # allow root only until better network permissions are available
3090 raise_param_exc({ migration_network => "Only root may use this option." })
3091 if $param->{migration_network} && $authuser ne 'root@pam';
3092
3ea94c60 3093 # test if VM exists
ffda963f 3094 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
3095
3096 # try to detect errors early
a5ed42d3 3097
ffda963f 3098 PVE::QemuConfig->check_lock($conf);
a5ed42d3 3099
3ea94c60 3100 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 3101 die "cant migrate running VM without --online\n"
3ea94c60
DM
3102 if !$param->{online};
3103 }
3104
47152e2e 3105 my $storecfg = PVE::Storage::config();
d80ad67f
AD
3106
3107 if( $param->{targetstorage}) {
3108 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
3109 } else {
3110 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
3111 }
47152e2e 3112
2003f0f8 3113 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 3114
88fc87b4
DM
3115 my $hacmd = sub {
3116 my $upid = shift;
3ea94c60 3117
c44291cd 3118 my $service = "vm:$vmid";
88fc87b4 3119
2003f0f8 3120 my $cmd = ['ha-manager', 'migrate', $service, $target];
88fc87b4 3121
02765844 3122 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4
DM
3123
3124 PVE::Tools::run_command($cmd);
3125
3126 return;
3127 };
3128
3129 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3130
3131 } else {
3132
f53c6ad8 3133 my $realcmd = sub {
f53c6ad8
DM
3134 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3135 };
88fc87b4 3136
f53c6ad8
DM
3137 my $worker = sub {
3138 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
3139 };
3140
f53c6ad8 3141 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 3142 }
3ea94c60 3143
3ea94c60 3144 }});
1e3baf05 3145
91c94f0a 3146__PACKAGE__->register_method({
afdb31d5
DM
3147 name => 'monitor',
3148 path => '{vmid}/monitor',
91c94f0a
DM
3149 method => 'POST',
3150 protected => 1,
3151 proxyto => 'node',
3152 description => "Execute Qemu monitor commands.",
a0d1b1a2 3153 permissions => {
a8f2f427 3154 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 3155 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 3156 },
91c94f0a
DM
3157 parameters => {
3158 additionalProperties => 0,
3159 properties => {
3160 node => get_standard_option('pve-node'),
3161 vmid => get_standard_option('pve-vmid'),
3162 command => {
3163 type => 'string',
3164 description => "The monitor command.",
3165 }
3166 },
3167 },
3168 returns => { type => 'string'},
3169 code => sub {
3170 my ($param) = @_;
3171
a8f2f427
FG
3172 my $rpcenv = PVE::RPCEnvironment::get();
3173 my $authuser = $rpcenv->get_user();
3174
3175 my $is_ro = sub {
3176 my $command = shift;
3177 return $command =~ m/^\s*info(\s+|$)/
3178 || $command =~ m/^\s*help\s*$/;
3179 };
3180
3181 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3182 if !&$is_ro($param->{command});
3183
91c94f0a
DM
3184 my $vmid = $param->{vmid};
3185
ffda963f 3186 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
3187
3188 my $res = '';
3189 eval {
7b7c6d1b 3190 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
3191 };
3192 $res = "ERROR: $@" if $@;
3193
3194 return $res;
3195 }});
3196
0d02881c
AD
3197__PACKAGE__->register_method({
3198 name => 'resize_vm',
614e3941 3199 path => '{vmid}/resize',
0d02881c
AD
3200 method => 'PUT',
3201 protected => 1,
3202 proxyto => 'node',
2f48a4f5 3203 description => "Extend volume size.",
0d02881c 3204 permissions => {
3b2773f6 3205 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3206 },
3207 parameters => {
3208 additionalProperties => 0,
2f48a4f5
DM
3209 properties => {
3210 node => get_standard_option('pve-node'),
335af808 3211 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3212 skiplock => get_standard_option('skiplock'),
3213 disk => {
3214 type => 'string',
3215 description => "The disk you want to resize.",
74479ee9 3216 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3217 },
3218 size => {
3219 type => 'string',
f91b2e45 3220 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3221 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
3222 },
3223 digest => {
3224 type => 'string',
3225 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3226 maxLength => 40,
3227 optional => 1,
3228 },
3229 },
0d02881c
AD
3230 },
3231 returns => { type => 'null'},
3232 code => sub {
3233 my ($param) = @_;
3234
3235 my $rpcenv = PVE::RPCEnvironment::get();
3236
3237 my $authuser = $rpcenv->get_user();
3238
3239 my $node = extract_param($param, 'node');
3240
3241 my $vmid = extract_param($param, 'vmid');
3242
3243 my $digest = extract_param($param, 'digest');
3244
2f48a4f5 3245 my $disk = extract_param($param, 'disk');
75466c4f 3246
2f48a4f5 3247 my $sizestr = extract_param($param, 'size');
0d02881c 3248
f91b2e45 3249 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3250 raise_param_exc({ skiplock => "Only root may use this option." })
3251 if $skiplock && $authuser ne 'root@pam';
3252
0d02881c
AD
3253 my $storecfg = PVE::Storage::config();
3254
0d02881c
AD
3255 my $updatefn = sub {
3256
ffda963f 3257 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3258
3259 die "checksum missmatch (file change by other user?)\n"
3260 if $digest && $digest ne $conf->{digest};
ffda963f 3261 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3262
f91b2e45
DM
3263 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3264
3265 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3266
d662790a
WL
3267 my (undef, undef, undef, undef, undef, undef, $format) =
3268 PVE::Storage::parse_volname($storecfg, $drive->{file});
3269
3270 die "can't resize volume: $disk if snapshot exists\n"
3271 if %{$conf->{snapshots}} && $format eq 'qcow2';
3272
f91b2e45
DM
3273 my $volid = $drive->{file};
3274
3275 die "disk '$disk' has no associated volume\n" if !$volid;
3276
3277 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3278
3279 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3280
3281 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3282
b572a606 3283 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3284 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3285
3286 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3287 my ($ext, $newsize, $unit) = ($1, $2, $4);
3288 if ($unit) {
3289 if ($unit eq 'K') {
3290 $newsize = $newsize * 1024;
3291 } elsif ($unit eq 'M') {
3292 $newsize = $newsize * 1024 * 1024;
3293 } elsif ($unit eq 'G') {
3294 $newsize = $newsize * 1024 * 1024 * 1024;
3295 } elsif ($unit eq 'T') {
3296 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3297 }
3298 }
3299 $newsize += $size if $ext;
3300 $newsize = int($newsize);
3301
9a478b17 3302 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3303
3304 return if $size == $newsize;
3305
2f48a4f5 3306 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3307
f91b2e45 3308 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3309
f91b2e45
DM
3310 $drive->{size} = $newsize;
3311 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3312
ffda963f 3313 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3314 };
0d02881c 3315
ffda963f 3316 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3317 return undef;
3318 }});
3319
9dbd1ee4 3320__PACKAGE__->register_method({
7e7d7b61 3321 name => 'snapshot_list',
9dbd1ee4 3322 path => '{vmid}/snapshot',
7e7d7b61
DM
3323 method => 'GET',
3324 description => "List all snapshots.",
3325 permissions => {
3326 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3327 },
3328 proxyto => 'node',
3329 protected => 1, # qemu pid files are only readable by root
3330 parameters => {
3331 additionalProperties => 0,
3332 properties => {
e261de40 3333 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3334 node => get_standard_option('pve-node'),
3335 },
3336 },
3337 returns => {
3338 type => 'array',
3339 items => {
3340 type => "object",
ce9b0a38
DM
3341 properties => {
3342 name => {
3343 description => "Snapshot identifier. Value 'current' identifies the current VM.",
3344 type => 'string',
3345 },
3346 vmstate => {
3347 description => "Snapshot includes RAM.",
3348 type => 'boolean',
3349 optional => 1,
3350 },
3351 description => {
3352 description => "Snapshot description.",
3353 type => 'string',
3354 },
3355 snaptime => {
3356 description => "Snapshot creation time",
3357 type => 'integer',
3358 renderer => 'timestamp',
3359 optional => 1,
3360 },
3361 parent => {
3362 description => "Parent snapshot identifier.",
3363 type => 'string',
3364 optional => 1,
3365 },
3366 },
7e7d7b61
DM
3367 },
3368 links => [ { rel => 'child', href => "{name}" } ],
3369 },
3370 code => sub {
3371 my ($param) = @_;
3372
6aa4651b
DM
3373 my $vmid = $param->{vmid};
3374
ffda963f 3375 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3376 my $snaphash = $conf->{snapshots} || {};
3377
3378 my $res = [];
3379
3380 foreach my $name (keys %$snaphash) {
0ea6bc69 3381 my $d = $snaphash->{$name};
75466c4f
DM
3382 my $item = {
3383 name => $name,
3384 snaptime => $d->{snaptime} || 0,
6aa4651b 3385 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3386 description => $d->{description} || '',
3387 };
0ea6bc69 3388 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3389 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3390 push @$res, $item;
3391 }
3392
6aa4651b 3393 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
ce9b0a38
DM
3394 my $current = {
3395 name => 'current',
3396 digest => $conf->{digest},
3397 running => $running,
3398 description => "You are here!",
3399 };
d1914468
DM
3400 $current->{parent} = $conf->{parent} if $conf->{parent};
3401
3402 push @$res, $current;
7e7d7b61
DM
3403
3404 return $res;
3405 }});
3406
3407__PACKAGE__->register_method({
3408 name => 'snapshot',
3409 path => '{vmid}/snapshot',
3410 method => 'POST',
9dbd1ee4
AD
3411 protected => 1,
3412 proxyto => 'node',
3413 description => "Snapshot a VM.",
3414 permissions => {
f1baf1df 3415 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3416 },
3417 parameters => {
3418 additionalProperties => 0,
3419 properties => {
3420 node => get_standard_option('pve-node'),
335af808 3421 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3422 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3423 vmstate => {
3424 optional => 1,
3425 type => 'boolean',
3426 description => "Save the vmstate",
3427 },
782f4f75
DM
3428 description => {
3429 optional => 1,
3430 type => 'string',
3431 description => "A textual description or comment.",
3432 },
9dbd1ee4
AD
3433 },
3434 },
7e7d7b61
DM
3435 returns => {
3436 type => 'string',
3437 description => "the task ID.",
3438 },
9dbd1ee4
AD
3439 code => sub {
3440 my ($param) = @_;
3441
3442 my $rpcenv = PVE::RPCEnvironment::get();
3443
3444 my $authuser = $rpcenv->get_user();
3445
3446 my $node = extract_param($param, 'node');
3447
3448 my $vmid = extract_param($param, 'vmid');
3449
9dbd1ee4
AD
3450 my $snapname = extract_param($param, 'snapname');
3451
d1914468
DM
3452 die "unable to use snapshot name 'current' (reserved name)\n"
3453 if $snapname eq 'current';
3454
7e7d7b61 3455 my $realcmd = sub {
22c377f0 3456 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
b2c9558d 3457 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3458 $param->{description});
7e7d7b61
DM
3459 };
3460
3461 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3462 }});
3463
154ccdcd
DM
3464__PACKAGE__->register_method({
3465 name => 'snapshot_cmd_idx',
3466 path => '{vmid}/snapshot/{snapname}',
3467 description => '',
3468 method => 'GET',
3469 permissions => {
3470 user => 'all',
3471 },
3472 parameters => {
3473 additionalProperties => 0,
3474 properties => {
3475 vmid => get_standard_option('pve-vmid'),
3476 node => get_standard_option('pve-node'),
8abd398b 3477 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3478 },
3479 },
3480 returns => {
3481 type => 'array',
3482 items => {
3483 type => "object",
3484 properties => {},
3485 },
3486 links => [ { rel => 'child', href => "{cmd}" } ],
3487 },
3488 code => sub {
3489 my ($param) = @_;
3490
3491 my $res = [];
3492
3493 push @$res, { cmd => 'rollback' };
d788cea6 3494 push @$res, { cmd => 'config' };
154ccdcd
DM
3495
3496 return $res;
3497 }});
3498
d788cea6
DM
3499__PACKAGE__->register_method({
3500 name => 'update_snapshot_config',
3501 path => '{vmid}/snapshot/{snapname}/config',
3502 method => 'PUT',
3503 protected => 1,
3504 proxyto => 'node',
3505 description => "Update snapshot metadata.",
3506 permissions => {
3507 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3508 },
3509 parameters => {
3510 additionalProperties => 0,
3511 properties => {
3512 node => get_standard_option('pve-node'),
3513 vmid => get_standard_option('pve-vmid'),
3514 snapname => get_standard_option('pve-snapshot-name'),
3515 description => {
3516 optional => 1,
3517 type => 'string',
3518 description => "A textual description or comment.",
3519 },
3520 },
3521 },
3522 returns => { type => 'null' },
3523 code => sub {
3524 my ($param) = @_;
3525
3526 my $rpcenv = PVE::RPCEnvironment::get();
3527
3528 my $authuser = $rpcenv->get_user();
3529
3530 my $vmid = extract_param($param, 'vmid');
3531
3532 my $snapname = extract_param($param, 'snapname');
3533
3534 return undef if !defined($param->{description});
3535
3536 my $updatefn = sub {
3537
ffda963f 3538 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3539
ffda963f 3540 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3541
3542 my $snap = $conf->{snapshots}->{$snapname};
3543
75466c4f
DM
3544 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3545
d788cea6
DM
3546 $snap->{description} = $param->{description} if defined($param->{description});
3547
ffda963f 3548 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3549 };
3550
ffda963f 3551 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3552
3553 return undef;
3554 }});
3555
3556__PACKAGE__->register_method({
3557 name => 'get_snapshot_config',
3558 path => '{vmid}/snapshot/{snapname}/config',
3559 method => 'GET',
3560 proxyto => 'node',
3561 description => "Get snapshot configuration",
3562 permissions => {
c268337d 3563 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
d788cea6
DM
3564 },
3565 parameters => {
3566 additionalProperties => 0,
3567 properties => {
3568 node => get_standard_option('pve-node'),
3569 vmid => get_standard_option('pve-vmid'),
3570 snapname => get_standard_option('pve-snapshot-name'),
3571 },
3572 },
3573 returns => { type => "object" },
3574 code => sub {
3575 my ($param) = @_;
3576
3577 my $rpcenv = PVE::RPCEnvironment::get();
3578
3579 my $authuser = $rpcenv->get_user();
3580
3581 my $vmid = extract_param($param, 'vmid');
3582
3583 my $snapname = extract_param($param, 'snapname');
3584
ffda963f 3585 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3586
3587 my $snap = $conf->{snapshots}->{$snapname};
3588
75466c4f
DM
3589 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3590
d788cea6
DM
3591 return $snap;
3592 }});
3593
7e7d7b61
DM
3594__PACKAGE__->register_method({
3595 name => 'rollback',
154ccdcd 3596 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3597 method => 'POST',
3598 protected => 1,
3599 proxyto => 'node',
3600 description => "Rollback VM state to specified snapshot.",
3601 permissions => {
c268337d 3602 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
7e7d7b61
DM
3603 },
3604 parameters => {
3605 additionalProperties => 0,
3606 properties => {
3607 node => get_standard_option('pve-node'),
335af808 3608 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3609 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3610 },
3611 },
3612 returns => {
3613 type => 'string',
3614 description => "the task ID.",
3615 },
3616 code => sub {
3617 my ($param) = @_;
3618
3619 my $rpcenv = PVE::RPCEnvironment::get();
3620
3621 my $authuser = $rpcenv->get_user();
3622
3623 my $node = extract_param($param, 'node');
3624
3625 my $vmid = extract_param($param, 'vmid');
3626
3627 my $snapname = extract_param($param, 'snapname');
3628
7e7d7b61 3629 my $realcmd = sub {
22c377f0 3630 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3631 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3632 };
3633
c068c1c3
WL
3634 my $worker = sub {
3635 # hold migration lock, this makes sure that nobody create replication snapshots
3636 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3637 };
3638
3639 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3640 }});
3641
3642__PACKAGE__->register_method({
3643 name => 'delsnapshot',
3644 path => '{vmid}/snapshot/{snapname}',
3645 method => 'DELETE',
3646 protected => 1,
3647 proxyto => 'node',
3648 description => "Delete a VM snapshot.",
3649 permissions => {
f1baf1df 3650 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3651 },
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 }),
8abd398b 3657 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3658 force => {
3659 optional => 1,
3660 type => 'boolean',
3661 description => "For removal from config file, even if removing disk snapshots fails.",
3662 },
7e7d7b61
DM
3663 },
3664 },
3665 returns => {
3666 type => 'string',
3667 description => "the task ID.",
3668 },
3669 code => sub {
3670 my ($param) = @_;
3671
3672 my $rpcenv = PVE::RPCEnvironment::get();
3673
3674 my $authuser = $rpcenv->get_user();
3675
3676 my $node = extract_param($param, 'node');
3677
3678 my $vmid = extract_param($param, 'vmid');
3679
3680 my $snapname = extract_param($param, 'snapname');
3681
7e7d7b61 3682 my $realcmd = sub {
22c377f0 3683 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3684 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3685 };
9dbd1ee4 3686
7b2257a8 3687 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3688 }});
3689
04a69bb4
AD
3690__PACKAGE__->register_method({
3691 name => 'template',
3692 path => '{vmid}/template',
3693 method => 'POST',
3694 protected => 1,
3695 proxyto => 'node',
3696 description => "Create a Template.",
b02691d8 3697 permissions => {
7af0a6c8
DM
3698 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3699 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3700 },
04a69bb4
AD
3701 parameters => {
3702 additionalProperties => 0,
3703 properties => {
3704 node => get_standard_option('pve-node'),
335af808 3705 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3706 disk => {
3707 optional => 1,
3708 type => 'string',
3709 description => "If you want to convert only 1 disk to base image.",
74479ee9 3710 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3711 },
3712
3713 },
3714 },
3715 returns => { type => 'null'},
3716 code => sub {
3717 my ($param) = @_;
3718
3719 my $rpcenv = PVE::RPCEnvironment::get();
3720
3721 my $authuser = $rpcenv->get_user();
3722
3723 my $node = extract_param($param, 'node');
3724
3725 my $vmid = extract_param($param, 'vmid');
3726
3727 my $disk = extract_param($param, 'disk');
3728
3729 my $updatefn = sub {
3730
ffda963f 3731 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3732
ffda963f 3733 PVE::QemuConfig->check_lock($conf);
04a69bb4 3734
75466c4f 3735 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3736 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3737
75466c4f 3738 die "you can't convert a template to a template\n"
ffda963f 3739 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3740
75466c4f 3741 die "you can't convert a VM to template if VM is running\n"
218cab9a 3742 if PVE::QemuServer::check_running($vmid);
35c5fdef 3743
04a69bb4
AD
3744 my $realcmd = sub {
3745 PVE::QemuServer::template_create($vmid, $conf, $disk);
3746 };
04a69bb4 3747
75e7e997 3748 $conf->{template} = 1;
ffda963f 3749 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3750
3751 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3752 };
3753
ffda963f 3754 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3755 return undef;
3756 }});
3757
1e3baf05 37581;