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