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