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