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