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