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