]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
d/control: bump version dependency to ha-manager
[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
1805fac3 2114 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
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
1805fac3 2270 my $timeout = $param->{timeout} // 60;
ae849692
DM
2271 my $hacmd = sub {
2272 my $upid = shift;
5fdbe4f0 2273
02765844 2274 print "Requesting HA stop for VM $vmid\n";
ae849692 2275
1805fac3 2276 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
ae849692 2277 PVE::Tools::run_command($cmd);
ae849692
DM
2278 return;
2279 };
2280
2281 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2282
2283 } else {
2284
2285 my $realcmd = sub {
2286 my $upid = shift;
2287
2288 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2289
ae849692
DM
2290 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2291 $shutdown, $param->{forceStop}, $keepActive);
ae849692
DM
2292 return;
2293 };
2294
2295 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2296 }
5fdbe4f0
DM
2297 }});
2298
165411f0
DC
2299__PACKAGE__->register_method({
2300 name => 'vm_reboot',
2301 path => '{vmid}/status/reboot',
2302 method => 'POST',
2303 protected => 1,
2304 proxyto => 'node',
2305 description => "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2306 permissions => {
2307 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2308 },
2309 parameters => {
2310 additionalProperties => 0,
2311 properties => {
2312 node => get_standard_option('pve-node'),
2313 vmid => get_standard_option('pve-vmid',
2314 { completion => \&PVE::QemuServer::complete_vmid_running }),
2315 timeout => {
2316 description => "Wait maximal timeout seconds for the shutdown.",
2317 type => 'integer',
2318 minimum => 0,
2319 optional => 1,
2320 },
2321 },
2322 },
2323 returns => {
2324 type => 'string',
2325 },
2326 code => sub {
2327 my ($param) = @_;
2328
2329 my $rpcenv = PVE::RPCEnvironment::get();
2330 my $authuser = $rpcenv->get_user();
2331
2332 my $node = extract_param($param, 'node');
2333 my $vmid = extract_param($param, 'vmid');
2334
2335 my $qmpstatus = eval {
2336 PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
2337 };
2338 my $err = $@ if $@;
2339
2340 if (!$err && $qmpstatus->{status} eq "paused") {
2341 die "VM is paused - cannot shutdown\n";
2342 }
2343
2344 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2345
2346 my $realcmd = sub {
2347 my $upid = shift;
2348
2349 syslog('info', "requesting reboot of VM $vmid: $upid\n");
2350 PVE::QemuServer::vm_reboot($vmid, $param->{timeout});
2351 return;
2352 };
2353
2354 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2355 }});
2356
5fdbe4f0 2357__PACKAGE__->register_method({
afdb31d5 2358 name => 'vm_suspend',
5fdbe4f0
DM
2359 path => '{vmid}/status/suspend',
2360 method => 'POST',
2361 protected => 1,
2362 proxyto => 'node',
2363 description => "Suspend virtual machine.",
a0d1b1a2
DM
2364 permissions => {
2365 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2366 },
5fdbe4f0
DM
2367 parameters => {
2368 additionalProperties => 0,
2369 properties => {
2370 node => get_standard_option('pve-node'),
ab5904f7
TL
2371 vmid => get_standard_option('pve-vmid',
2372 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2373 skiplock => get_standard_option('skiplock'),
22371fe0
DC
2374 todisk => {
2375 type => 'boolean',
2376 default => 0,
2377 optional => 1,
2378 description => 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2379 },
48b4cdc2
DC
2380 statestorage => get_standard_option('pve-storage-id', {
2381 description => "The storage for the VM state",
2382 requires => 'todisk',
2383 optional => 1,
2384 completion => \&PVE::Storage::complete_storage_enabled,
2385 }),
5fdbe4f0
DM
2386 },
2387 },
afdb31d5 2388 returns => {
5fdbe4f0
DM
2389 type => 'string',
2390 },
2391 code => sub {
2392 my ($param) = @_;
2393
2394 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 2395 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2396
2397 my $node = extract_param($param, 'node');
5fdbe4f0
DM
2398 my $vmid = extract_param($param, 'vmid');
2399
22371fe0
DC
2400 my $todisk = extract_param($param, 'todisk') // 0;
2401
48b4cdc2
DC
2402 my $statestorage = extract_param($param, 'statestorage');
2403
5fdbe4f0 2404 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2405 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2406 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2407
ff1a2432
DM
2408 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2409
22371fe0
DC
2410 die "Cannot suspend HA managed VM to disk\n"
2411 if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid);
2412
5fdbe4f0
DM
2413 my $realcmd = sub {
2414 my $upid = shift;
2415
2416 syslog('info', "suspend VM $vmid: $upid\n");
2417
48b4cdc2 2418 PVE::QemuServer::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
5fdbe4f0
DM
2419
2420 return;
2421 };
2422
a4262553 2423 my $taskname = $todisk ? 'qmsuspend' : 'qmpause';
f17fb184 2424 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2425 }});
2426
2427__PACKAGE__->register_method({
afdb31d5 2428 name => 'vm_resume',
5fdbe4f0
DM
2429 path => '{vmid}/status/resume',
2430 method => 'POST',
2431 protected => 1,
2432 proxyto => 'node',
2433 description => "Resume virtual machine.",
a0d1b1a2
DM
2434 permissions => {
2435 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2436 },
5fdbe4f0
DM
2437 parameters => {
2438 additionalProperties => 0,
2439 properties => {
2440 node => get_standard_option('pve-node'),
ab5904f7
TL
2441 vmid => get_standard_option('pve-vmid',
2442 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2443 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2444 nocheck => { type => 'boolean', optional => 1 },
2445
5fdbe4f0
DM
2446 },
2447 },
afdb31d5 2448 returns => {
5fdbe4f0
DM
2449 type => 'string',
2450 },
2451 code => sub {
2452 my ($param) = @_;
2453
2454 my $rpcenv = PVE::RPCEnvironment::get();
2455
a0d1b1a2 2456 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2457
2458 my $node = extract_param($param, 'node');
2459
2460 my $vmid = extract_param($param, 'vmid');
2461
2462 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2463 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2464 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2465
289e0b85
AD
2466 my $nocheck = extract_param($param, 'nocheck');
2467
cd9a035b
TL
2468 my $to_disk_suspended;
2469 eval {
2470 PVE::QemuConfig->lock_config($vmid, sub {
2471 my $conf = PVE::QemuConfig->load_config($vmid);
2472 $to_disk_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
2473 });
2474 };
2475
2476 die "VM $vmid not running\n"
2477 if !$to_disk_suspended && !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2478
5fdbe4f0
DM
2479 my $realcmd = sub {
2480 my $upid = shift;
2481
2482 syslog('info', "resume VM $vmid: $upid\n");
2483
cd9a035b
TL
2484 if (!$to_disk_suspended) {
2485 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
2486 } else {
2487 my $storecfg = PVE::Storage::config();
2488 PVE::QemuServer::vm_start($storecfg, $vmid, undef, $skiplock);
2489 }
1e3baf05 2490
5fdbe4f0
DM
2491 return;
2492 };
2493
a0d1b1a2 2494 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2495 }});
2496
2497__PACKAGE__->register_method({
afdb31d5 2498 name => 'vm_sendkey',
5fdbe4f0
DM
2499 path => '{vmid}/sendkey',
2500 method => 'PUT',
2501 protected => 1,
2502 proxyto => 'node',
2503 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2504 permissions => {
2505 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2506 },
5fdbe4f0
DM
2507 parameters => {
2508 additionalProperties => 0,
2509 properties => {
2510 node => get_standard_option('pve-node'),
ab5904f7
TL
2511 vmid => get_standard_option('pve-vmid',
2512 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2513 skiplock => get_standard_option('skiplock'),
2514 key => {
2515 description => "The key (qemu monitor encoding).",
2516 type => 'string'
2517 }
2518 },
2519 },
2520 returns => { type => 'null'},
2521 code => sub {
2522 my ($param) = @_;
2523
2524 my $rpcenv = PVE::RPCEnvironment::get();
2525
a0d1b1a2 2526 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2527
2528 my $node = extract_param($param, 'node');
2529
2530 my $vmid = extract_param($param, 'vmid');
2531
2532 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2533 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2534 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2535
2536 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2537
2538 return;
1e3baf05
DM
2539 }});
2540
1ac0d2ee
AD
2541__PACKAGE__->register_method({
2542 name => 'vm_feature',
2543 path => '{vmid}/feature',
2544 method => 'GET',
2545 proxyto => 'node',
75466c4f 2546 protected => 1,
1ac0d2ee
AD
2547 description => "Check if feature for virtual machine is available.",
2548 permissions => {
2549 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2550 },
2551 parameters => {
2552 additionalProperties => 0,
2553 properties => {
2554 node => get_standard_option('pve-node'),
2555 vmid => get_standard_option('pve-vmid'),
2556 feature => {
2557 description => "Feature to check.",
2558 type => 'string',
7758ce86 2559 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2560 },
2561 snapname => get_standard_option('pve-snapshot-name', {
2562 optional => 1,
2563 }),
2564 },
1ac0d2ee
AD
2565 },
2566 returns => {
719893a9
DM
2567 type => "object",
2568 properties => {
2569 hasFeature => { type => 'boolean' },
7043d946 2570 nodes => {
719893a9
DM
2571 type => 'array',
2572 items => { type => 'string' },
2573 }
2574 },
1ac0d2ee
AD
2575 },
2576 code => sub {
2577 my ($param) = @_;
2578
2579 my $node = extract_param($param, 'node');
2580
2581 my $vmid = extract_param($param, 'vmid');
2582
2583 my $snapname = extract_param($param, 'snapname');
2584
2585 my $feature = extract_param($param, 'feature');
2586
2587 my $running = PVE::QemuServer::check_running($vmid);
2588
ffda963f 2589 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2590
2591 if($snapname){
2592 my $snap = $conf->{snapshots}->{$snapname};
2593 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2594 $conf = $snap;
2595 }
2596 my $storecfg = PVE::Storage::config();
2597
719893a9 2598 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2599 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2600
719893a9
DM
2601 return {
2602 hasFeature => $hasFeature,
2603 nodes => [ keys %$nodelist ],
7043d946 2604 };
1ac0d2ee
AD
2605 }});
2606
6116f729 2607__PACKAGE__->register_method({
9418baad
DM
2608 name => 'clone_vm',
2609 path => '{vmid}/clone',
6116f729
DM
2610 method => 'POST',
2611 protected => 1,
2612 proxyto => 'node',
37329185 2613 description => "Create a copy of virtual machine/template.",
6116f729 2614 permissions => {
9418baad 2615 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2616 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2617 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2618 check =>
2619 [ 'and',
9418baad 2620 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2621 [ 'or',
6116f729
DM
2622 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2623 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2624 ],
2625 ]
2626 },
2627 parameters => {
2628 additionalProperties => 0,
2629 properties => {
6116f729 2630 node => get_standard_option('pve-node'),
335af808 2631 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1ae43f8c
DM
2632 newid => get_standard_option('pve-vmid', {
2633 completion => \&PVE::Cluster::complete_next_vmid,
2634 description => 'VMID for the clone.' }),
a60ab1a6
DM
2635 name => {
2636 optional => 1,
2637 type => 'string', format => 'dns-name',
2638 description => "Set a name for the new VM.",
2639 },
2640 description => {
2641 optional => 1,
2642 type => 'string',
2643 description => "Description for the new VM.",
2644 },
75466c4f 2645 pool => {
6116f729
DM
2646 optional => 1,
2647 type => 'string', format => 'pve-poolid',
2648 description => "Add the new VM to the specified pool.",
2649 },
9076d880 2650 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2651 optional => 1,
2652 }),
81f043eb 2653 storage => get_standard_option('pve-storage-id', {
9418baad 2654 description => "Target storage for full clone.",
81f043eb
AD
2655 optional => 1,
2656 }),
55173c6b 2657 'format' => {
fd13b1d0 2658 description => "Target format for file storage. Only valid for full clone.",
42a19c87
AD
2659 type => 'string',
2660 optional => 1,
55173c6b 2661 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2662 },
6116f729
DM
2663 full => {
2664 optional => 1,
55173c6b 2665 type => 'boolean',
fd13b1d0 2666 description => "Create a full copy of all disks. This is always done when " .
9418baad 2667 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729 2668 },
75466c4f 2669 target => get_standard_option('pve-node', {
55173c6b
DM
2670 description => "Target node. Only allowed if the original VM is on shared storage.",
2671 optional => 1,
2672 }),
0aab5a16
SI
2673 bwlimit => {
2674 description => "Override I/O bandwidth limit (in KiB/s).",
2675 optional => 1,
2676 type => 'integer',
2677 minimum => '0',
41756a3b 2678 default => 'clone limit from datacenter or storage config',
0aab5a16 2679 },
55173c6b 2680 },
6116f729
DM
2681 },
2682 returns => {
2683 type => 'string',
2684 },
2685 code => sub {
2686 my ($param) = @_;
2687
2688 my $rpcenv = PVE::RPCEnvironment::get();
2689
55173c6b 2690 my $authuser = $rpcenv->get_user();
6116f729
DM
2691
2692 my $node = extract_param($param, 'node');
2693
2694 my $vmid = extract_param($param, 'vmid');
2695
2696 my $newid = extract_param($param, 'newid');
2697
6116f729
DM
2698 my $pool = extract_param($param, 'pool');
2699
2700 if (defined($pool)) {
2701 $rpcenv->check_pool_exist($pool);
2702 }
2703
55173c6b 2704 my $snapname = extract_param($param, 'snapname');
9076d880 2705
81f043eb
AD
2706 my $storage = extract_param($param, 'storage');
2707
42a19c87
AD
2708 my $format = extract_param($param, 'format');
2709
55173c6b
DM
2710 my $target = extract_param($param, 'target');
2711
2712 my $localnode = PVE::INotify::nodename();
2713
751cc556 2714 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2715
2716 PVE::Cluster::check_node_exists($target) if $target;
2717
6116f729
DM
2718 my $storecfg = PVE::Storage::config();
2719
4a5a2590
DM
2720 if ($storage) {
2721 # check if storage is enabled on local node
2722 PVE::Storage::storage_check_enabled($storecfg, $storage);
2723 if ($target) {
2724 # check if storage is available on target node
2725 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2726 # clone only works if target storage is shared
2727 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2728 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2729 }
2730 }
2731
55173c6b 2732 PVE::Cluster::check_cfs_quorum();
6116f729 2733
4e4f83fe
DM
2734 my $running = PVE::QemuServer::check_running($vmid) || 0;
2735
4e4f83fe
DM
2736 # exclusive lock if VM is running - else shared lock is enough;
2737 my $shared_lock = $running ? 0 : 1;
2738
9418baad 2739 my $clonefn = sub {
6116f729 2740
829967a9
DM
2741 # do all tests after lock
2742 # we also try to do all tests before we fork the worker
2743
ffda963f 2744 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2745
ffda963f 2746 PVE::QemuConfig->check_lock($conf);
6116f729 2747
4e4f83fe 2748 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2749
4e4f83fe 2750 die "unexpected state change\n" if $verify_running != $running;
6116f729 2751
75466c4f
DM
2752 die "snapshot '$snapname' does not exist\n"
2753 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2754
fd13b1d0
DM
2755 my $full = extract_param($param, 'full');
2756 if (!defined($full)) {
2757 $full = !PVE::QemuConfig->is_template($conf);
2758 }
2759
2760 die "parameter 'storage' not allowed for linked clones\n"
2761 if defined($storage) && !$full;
2762
2763 die "parameter 'format' not allowed for linked clones\n"
2764 if defined($format) && !$full;
2765
75466c4f 2766 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2767
9418baad 2768 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2769
9418baad 2770 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2771
ffda963f 2772 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2773
2774 die "unable to create VM $newid: config file already exists\n"
2775 if -f $conffile;
2776
9418baad 2777 my $newconf = { lock => 'clone' };
829967a9 2778 my $drives = {};
34456bf0 2779 my $fullclone = {};
829967a9
DM
2780 my $vollist = [];
2781
2782 foreach my $opt (keys %$oldconf) {
2783 my $value = $oldconf->{$opt};
2784
2785 # do not copy snapshot related info
2786 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2787 $opt eq 'vmstate' || $opt eq 'snapstate';
2788
a78ea5df
WL
2789 # no need to copy unused images, because VMID(owner) changes anyways
2790 next if $opt =~ m/^unused\d+$/;
2791
829967a9
DM
2792 # always change MAC! address
2793 if ($opt =~ m/^net(\d+)$/) {
2794 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2795 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2796 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2797 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2798 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2799 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2800 die "unable to parse drive options for '$opt'\n" if !$drive;
7d6c99f0 2801 if (PVE::QemuServer::drive_is_cdrom($drive)) {
829967a9
DM
2802 $newconf->{$opt} = $value; # simply copy configuration
2803 } else {
7d6c99f0 2804 if ($full) {
6318daca 2805 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2806 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2807 $fullclone->{$opt} = 1;
64ff6fe4
SP
2808 } else {
2809 # not full means clone instead of copy
6318daca 2810 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2811 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2812 }
829967a9
DM
2813 $drives->{$opt} = $drive;
2814 push @$vollist, $drive->{file};
2815 }
2816 } else {
2817 # copy everything else
2818 $newconf->{$opt} = $value;
2819 }
2820 }
2821
cd11416f 2822 # auto generate a new uuid
cd11416f 2823 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
6ee499ff 2824 $smbios1->{uuid} = PVE::QemuServer::generate_uuid();
cd11416f
DM
2825 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2826
6ee499ff
DC
2827 # auto generate a new vmgenid if the option was set
2828 if ($newconf->{vmgenid}) {
2829 $newconf->{vmgenid} = PVE::QemuServer::generate_uuid();
2830 }
2831
829967a9
DM
2832 delete $newconf->{template};
2833
2834 if ($param->{name}) {
2835 $newconf->{name} = $param->{name};
2836 } else {
c55fee03
DM
2837 if ($oldconf->{name}) {
2838 $newconf->{name} = "Copy-of-$oldconf->{name}";
2839 } else {
2840 $newconf->{name} = "Copy-of-VM-$vmid";
2841 }
829967a9 2842 }
2dd53043 2843
829967a9
DM
2844 if ($param->{description}) {
2845 $newconf->{description} = $param->{description};
2846 }
2847
6116f729 2848 # create empty/temp config - this fails if VM already exists on other node
9418baad 2849 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2850
2851 my $realcmd = sub {
2852 my $upid = shift;
2853
b83e0181 2854 my $newvollist = [];
c6fdd002 2855 my $jobs = {};
6116f729 2856
b83e0181 2857 eval {
eaae66be
TL
2858 local $SIG{INT} =
2859 local $SIG{TERM} =
2860 local $SIG{QUIT} =
2861 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2862
eb15b9f0 2863 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2864
0aab5a16
SI
2865 my $bwlimit = extract_param($param, 'bwlimit');
2866
c6fdd002
AD
2867 my $total_jobs = scalar(keys %{$drives});
2868 my $i = 1;
c6fdd002 2869
829967a9
DM
2870 foreach my $opt (keys %$drives) {
2871 my $drive = $drives->{$opt};
3b4cf0f0 2872 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2873
0aab5a16 2874 my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
f0dbdb68
TL
2875 my $storage_list = [ $src_sid ];
2876 push @$storage_list, $storage if defined($storage);
ee43cd48 2877 my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
0aab5a16 2878
152fe752 2879 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0 2880 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
0aab5a16 2881 $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit);
00b095ca 2882
152fe752 2883 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2884
ffda963f 2885 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2886 $i++;
829967a9 2887 }
b83e0181
DM
2888
2889 delete $newconf->{lock};
68e46b84
DC
2890
2891 # do not write pending changes
c725dd5f
DC
2892 if (my @changes = keys %{$newconf->{pending}}) {
2893 my $pending = join(',', @changes);
2894 warn "found pending changes for '$pending', discarding for clone\n";
68e46b84
DC
2895 delete $newconf->{pending};
2896 }
2897
ffda963f 2898 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2899
2900 if ($target) {
baca276d 2901 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2902 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2903 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2904
ffda963f 2905 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2906 die "Failed to move config to node '$target' - rename failed: $!\n"
2907 if !rename($conffile, $newconffile);
2908 }
d703d4c0 2909
be517049 2910 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2911 };
75466c4f 2912 if (my $err = $@) {
6116f729
DM
2913 unlink $conffile;
2914
c6fdd002
AD
2915 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2916
b83e0181
DM
2917 sleep 1; # some storage like rbd need to wait before release volume - really?
2918
2919 foreach my $volid (@$newvollist) {
2920 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2921 warn $@ if $@;
2922 }
9418baad 2923 die "clone failed: $err";
6116f729
DM
2924 }
2925
2926 return;
2927 };
2928
457010cc
AG
2929 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2930
9418baad 2931 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2932 };
2933
ffda963f 2934 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2935 # Aquire exclusive lock lock for $newid
ffda963f 2936 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2937 });
2938
2939 }});
2940
586bfa78 2941__PACKAGE__->register_method({
43bc02a9
DM
2942 name => 'move_vm_disk',
2943 path => '{vmid}/move_disk',
e2cd75fa 2944 method => 'POST',
586bfa78
AD
2945 protected => 1,
2946 proxyto => 'node',
2947 description => "Move volume to different storage.",
2948 permissions => {
c07a9e3d
DM
2949 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2950 check => [ 'and',
2951 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2952 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2953 ],
586bfa78
AD
2954 },
2955 parameters => {
2956 additionalProperties => 0,
c07a9e3d 2957 properties => {
586bfa78 2958 node => get_standard_option('pve-node'),
335af808 2959 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2960 disk => {
2961 type => 'string',
2962 description => "The disk you want to move.",
74479ee9 2963 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2964 },
335af808
DM
2965 storage => get_standard_option('pve-storage-id', {
2966 description => "Target storage.",
2967 completion => \&PVE::QemuServer::complete_storage,
2968 }),
635c3c44 2969 'format' => {
586bfa78
AD
2970 type => 'string',
2971 description => "Target Format.",
2972 enum => [ 'raw', 'qcow2', 'vmdk' ],
2973 optional => 1,
2974 },
70d45e33
DM
2975 delete => {
2976 type => 'boolean',
2977 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2978 optional => 1,
2979 default => 0,
2980 },
586bfa78
AD
2981 digest => {
2982 type => 'string',
2983 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2984 maxLength => 40,
2985 optional => 1,
2986 },
0aab5a16
SI
2987 bwlimit => {
2988 description => "Override I/O bandwidth limit (in KiB/s).",
2989 optional => 1,
2990 type => 'integer',
2991 minimum => '0',
41756a3b 2992 default => 'move limit from datacenter or storage config',
0aab5a16 2993 },
586bfa78
AD
2994 },
2995 },
e2cd75fa
DM
2996 returns => {
2997 type => 'string',
2998 description => "the task ID.",
2999 },
586bfa78
AD
3000 code => sub {
3001 my ($param) = @_;
3002
3003 my $rpcenv = PVE::RPCEnvironment::get();
3004
3005 my $authuser = $rpcenv->get_user();
3006
3007 my $node = extract_param($param, 'node');
3008
3009 my $vmid = extract_param($param, 'vmid');
3010
3011 my $digest = extract_param($param, 'digest');
3012
3013 my $disk = extract_param($param, 'disk');
3014
3015 my $storeid = extract_param($param, 'storage');
3016
3017 my $format = extract_param($param, 'format');
3018
586bfa78
AD
3019 my $storecfg = PVE::Storage::config();
3020
3021 my $updatefn = sub {
3022
ffda963f 3023 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 3024
dcce9b46
FG
3025 PVE::QemuConfig->check_lock($conf);
3026
586bfa78
AD
3027 die "checksum missmatch (file change by other user?)\n"
3028 if $digest && $digest ne $conf->{digest};
586bfa78
AD
3029
3030 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3031
3032 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3033
70d45e33 3034 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78 3035
931432bd 3036 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1);
586bfa78 3037
e2cd75fa 3038 my $oldfmt;
70d45e33 3039 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
3040 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3041 $oldfmt = $1;
3042 }
3043
7043d946 3044 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 3045 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 3046
9dbf9b54
FG
3047 # this only checks snapshots because $disk is passed!
3048 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
3049 die "you can't move a disk with snapshots and delete the source\n"
3050 if $snapshotted && $param->{delete};
3051
586bfa78
AD
3052 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3053
3054 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
3055
3056 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
3057
586bfa78
AD
3058 my $realcmd = sub {
3059
3060 my $newvollist = [];
3061
3062 eval {
6cb0144a
EK
3063 local $SIG{INT} =
3064 local $SIG{TERM} =
3065 local $SIG{QUIT} =
3066 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
586bfa78 3067
9dbf9b54
FG
3068 warn "moving disk with snapshots, snapshots will not be moved!\n"
3069 if $snapshotted;
3070
0aab5a16
SI
3071 my $bwlimit = extract_param($param, 'bwlimit');
3072 my $movelimit = PVE::Storage::get_bandwidth_limit('move', [$oldstoreid, $storeid], $bwlimit);
3073
e2cd75fa 3074 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
0aab5a16 3075 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
e2cd75fa
DM
3076
3077 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
3078
8793d495 3079 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 3080
fbd7dcce
FG
3081 # convert moved disk to base if part of template
3082 PVE::QemuServer::template_create($vmid, $conf, $disk)
3083 if PVE::QemuConfig->is_template($conf);
3084
ffda963f 3085 PVE::QemuConfig->write_config($vmid, $conf);
73272365 3086
ca662131
SI
3087 if ($running && PVE::QemuServer::parse_guest_agent($conf)->{fstrim_cloned_disks} && PVE::QemuServer::qga_check_running($vmid)) {
3088 eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fstrim"); };
3089 }
3090
f34ebd52 3091 eval {
73272365 3092 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 3093 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
3094 if !$running;
3095 };
3096 warn $@ if $@;
586bfa78
AD
3097 };
3098 if (my $err = $@) {
3099
3100 foreach my $volid (@$newvollist) {
3101 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
3102 warn $@ if $@;
3103 }
3104 die "storage migration failed: $err";
3105 }
70d45e33
DM
3106
3107 if ($param->{delete}) {
a3d0bafb
FG
3108 eval {
3109 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
3110 PVE::Storage::vdisk_free($storecfg, $old_volid);
3111 };
3112 warn $@ if $@;
70d45e33 3113 }
586bfa78
AD
3114 };
3115
3116 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3117 };
e2cd75fa 3118
ffda963f 3119 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
3120 }});
3121
71fc647f
TM
3122my $check_vm_disks_local = sub {
3123 my ($storecfg, $vmconf, $vmid) = @_;
3124
3125 my $local_disks = {};
3126
3127 # add some more information to the disks e.g. cdrom
3128 PVE::QemuServer::foreach_volid($vmconf, sub {
3129 my ($volid, $attr) = @_;
3130
3131 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
3132 if ($storeid) {
3133 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
3134 return if $scfg->{shared};
3135 }
3136 # The shared attr here is just a special case where the vdisk
3137 # is marked as shared manually
3138 return if $attr->{shared};
3139 return if $attr->{cdrom} and $volid eq "none";
3140
3141 if (exists $local_disks->{$volid}) {
3142 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3143 } else {
3144 $local_disks->{$volid} = $attr;
3145 # ensure volid is present in case it's needed
3146 $local_disks->{$volid}->{volid} = $volid;
3147 }
3148 });
3149
3150 return $local_disks;
3151};
3152
3153__PACKAGE__->register_method({
3154 name => 'migrate_vm_precondition',
3155 path => '{vmid}/migrate',
3156 method => 'GET',
3157 protected => 1,
3158 proxyto => 'node',
3159 description => "Get preconditions for migration.",
3160 permissions => {
3161 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3162 },
3163 parameters => {
3164 additionalProperties => 0,
3165 properties => {
3166 node => get_standard_option('pve-node'),
3167 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
3168 target => get_standard_option('pve-node', {
3169 description => "Target node.",
3170 completion => \&PVE::Cluster::complete_migration_target,
3171 optional => 1,
3172 }),
3173 },
3174 },
3175 returns => {
3176 type => "object",
3177 properties => {
3178 running => { type => 'boolean' },
3179 allowed_nodes => {
3180 type => 'array',
3181 optional => 1,
f25852c2
TM
3182 description => "List nodes allowed for offline migration, only passed if VM is offline"
3183 },
3184 not_allowed_nodes => {
3185 type => 'object',
3186 optional => 1,
3187 description => "List not allowed nodes with additional informations, only passed if VM is offline"
71fc647f
TM
3188 },
3189 local_disks => {
3190 type => 'array',
3191 description => "List local disks including CD-Rom, unsused and not referenced disks"
3192 },
3193 local_resources => {
3194 type => 'array',
3195 description => "List local resources e.g. pci, usb"
3196 }
3197 },
3198 },
3199 code => sub {
3200 my ($param) = @_;
3201
3202 my $rpcenv = PVE::RPCEnvironment::get();
3203
3204 my $authuser = $rpcenv->get_user();
3205
3206 PVE::Cluster::check_cfs_quorum();
3207
3208 my $res = {};
3209
3210 my $vmid = extract_param($param, 'vmid');
3211 my $target = extract_param($param, 'target');
3212 my $localnode = PVE::INotify::nodename();
3213
3214
3215 # test if VM exists
3216 my $vmconf = PVE::QemuConfig->load_config($vmid);
3217 my $storecfg = PVE::Storage::config();
3218
3219
3220 # try to detect errors early
3221 PVE::QemuConfig->check_lock($vmconf);
3222
3223 $res->{running} = PVE::QemuServer::check_running($vmid) ? 1:0;
3224
3225 # if vm is not running, return target nodes where local storage is available
3226 # for offline migration
3227 if (!$res->{running}) {
f25852c2
TM
3228 $res->{allowed_nodes} = [];
3229 my $checked_nodes = PVE::QemuServer::check_local_storage_availability($vmconf, $storecfg);
32075a2c 3230 delete $checked_nodes->{$localnode};
f25852c2 3231
f25852c2 3232 foreach my $node (keys %$checked_nodes) {
32075a2c 3233 if (!defined $checked_nodes->{$node}->{unavailable_storages}) {
f25852c2 3234 push @{$res->{allowed_nodes}}, $node;
f25852c2 3235 }
71fc647f 3236
f25852c2
TM
3237 }
3238 $res->{not_allowed_nodes} = $checked_nodes;
71fc647f
TM
3239 }
3240
3241
3242 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3243 $res->{local_disks} = [ values %$local_disks ];;
3244
3245 my $local_resources = PVE::QemuServer::check_local_resources($vmconf, 1);
3246
3247 $res->{local_resources} = $local_resources;
3248
3249 return $res;
3250
3251
3252 }});
3253
3ea94c60 3254__PACKAGE__->register_method({
afdb31d5 3255 name => 'migrate_vm',
3ea94c60
DM
3256 path => '{vmid}/migrate',
3257 method => 'POST',
3258 protected => 1,
3259 proxyto => 'node',
3260 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
3261 permissions => {
3262 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3263 },
3ea94c60
DM
3264 parameters => {
3265 additionalProperties => 0,
3266 properties => {
3267 node => get_standard_option('pve-node'),
335af808 3268 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
c2ed338e 3269 target => get_standard_option('pve-node', {
335af808
DM
3270 description => "Target node.",
3271 completion => \&PVE::Cluster::complete_migration_target,
3272 }),
3ea94c60
DM
3273 online => {
3274 type => 'boolean',
13739386 3275 description => "Use online/live migration if VM is running. Ignored if VM is stopped.",
3ea94c60
DM
3276 optional => 1,
3277 },
3278 force => {
3279 type => 'boolean',
3280 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
3281 optional => 1,
3282 },
2de2d6f7
TL
3283 migration_type => {
3284 type => 'string',
3285 enum => ['secure', 'insecure'],
c07a9e3d 3286 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
3287 optional => 1,
3288 },
3289 migration_network => {
c07a9e3d 3290 type => 'string', format => 'CIDR',
2de2d6f7
TL
3291 description => "CIDR of the (sub) network that is used for migration.",
3292 optional => 1,
3293 },
56af7146
AD
3294 "with-local-disks" => {
3295 type => 'boolean',
3296 description => "Enable live storage migration for local disk",
b74cad8a 3297 optional => 1,
56af7146
AD
3298 },
3299 targetstorage => get_standard_option('pve-storage-id', {
3300 description => "Default target storage.",
3301 optional => 1,
3302 completion => \&PVE::QemuServer::complete_storage,
3303 }),
0aab5a16
SI
3304 bwlimit => {
3305 description => "Override I/O bandwidth limit (in KiB/s).",
3306 optional => 1,
3307 type => 'integer',
3308 minimum => '0',
41756a3b 3309 default => 'migrate limit from datacenter or storage config',
0aab5a16 3310 },
3ea94c60
DM
3311 },
3312 },
afdb31d5 3313 returns => {
3ea94c60
DM
3314 type => 'string',
3315 description => "the task ID.",
3316 },
3317 code => sub {
3318 my ($param) = @_;
3319
3320 my $rpcenv = PVE::RPCEnvironment::get();
a0d1b1a2 3321 my $authuser = $rpcenv->get_user();
3ea94c60
DM
3322
3323 my $target = extract_param($param, 'target');
3324
3325 my $localnode = PVE::INotify::nodename();
3326 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
3327
3328 PVE::Cluster::check_cfs_quorum();
3329
3330 PVE::Cluster::check_node_exists($target);
3331
3332 my $targetip = PVE::Cluster::remote_node_ip($target);
3333
3334 my $vmid = extract_param($param, 'vmid');
3335
afdb31d5 3336 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 3337 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 3338
2de2d6f7
TL
3339 raise_param_exc({ migration_type => "Only root may use this option." })
3340 if $param->{migration_type} && $authuser ne 'root@pam';
3341
3342 # allow root only until better network permissions are available
3343 raise_param_exc({ migration_network => "Only root may use this option." })
3344 if $param->{migration_network} && $authuser ne 'root@pam';
3345
3ea94c60 3346 # test if VM exists
ffda963f 3347 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
3348
3349 # try to detect errors early
a5ed42d3 3350
ffda963f 3351 PVE::QemuConfig->check_lock($conf);
a5ed42d3 3352
3ea94c60 3353 if (PVE::QemuServer::check_running($vmid)) {
fda72913 3354 die "can't migrate running VM without --online\n" if !$param->{online};
13739386 3355 } else {
fda72913 3356 warn "VM isn't running. Doing offline migration instead\n." if $param->{online};
13739386 3357 $param->{online} = 0;
3ea94c60
DM
3358 }
3359
13739386
FE
3360 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
3361 if !$param->{online} && $param->{targetstorage};
3362
47152e2e 3363 my $storecfg = PVE::Storage::config();
d80ad67f
AD
3364
3365 if( $param->{targetstorage}) {
3366 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
3367 } else {
3368 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
3369 }
47152e2e 3370
2003f0f8 3371 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 3372
88fc87b4
DM
3373 my $hacmd = sub {
3374 my $upid = shift;
3ea94c60 3375
02765844 3376 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4 3377
a4262553 3378 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
88fc87b4 3379 PVE::Tools::run_command($cmd);
88fc87b4
DM
3380 return;
3381 };
3382
3383 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3384
3385 } else {
3386
f53c6ad8 3387 my $realcmd = sub {
f53c6ad8
DM
3388 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
3389 };
88fc87b4 3390
f53c6ad8
DM
3391 my $worker = sub {
3392 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
3393 };
3394
f53c6ad8 3395 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 3396 }
3ea94c60 3397
3ea94c60 3398 }});
1e3baf05 3399
91c94f0a 3400__PACKAGE__->register_method({
afdb31d5
DM
3401 name => 'monitor',
3402 path => '{vmid}/monitor',
91c94f0a
DM
3403 method => 'POST',
3404 protected => 1,
3405 proxyto => 'node',
3406 description => "Execute Qemu monitor commands.",
a0d1b1a2 3407 permissions => {
a8f2f427 3408 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 3409 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 3410 },
91c94f0a
DM
3411 parameters => {
3412 additionalProperties => 0,
3413 properties => {
3414 node => get_standard_option('pve-node'),
3415 vmid => get_standard_option('pve-vmid'),
3416 command => {
3417 type => 'string',
3418 description => "The monitor command.",
3419 }
3420 },
3421 },
3422 returns => { type => 'string'},
3423 code => sub {
3424 my ($param) = @_;
3425
a8f2f427
FG
3426 my $rpcenv = PVE::RPCEnvironment::get();
3427 my $authuser = $rpcenv->get_user();
3428
3429 my $is_ro = sub {
3430 my $command = shift;
3431 return $command =~ m/^\s*info(\s+|$)/
3432 || $command =~ m/^\s*help\s*$/;
3433 };
3434
3435 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3436 if !&$is_ro($param->{command});
3437
91c94f0a
DM
3438 my $vmid = $param->{vmid};
3439
ffda963f 3440 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
3441
3442 my $res = '';
3443 eval {
7b7c6d1b 3444 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
3445 };
3446 $res = "ERROR: $@" if $@;
3447
3448 return $res;
3449 }});
3450
0d02881c
AD
3451__PACKAGE__->register_method({
3452 name => 'resize_vm',
614e3941 3453 path => '{vmid}/resize',
0d02881c
AD
3454 method => 'PUT',
3455 protected => 1,
3456 proxyto => 'node',
2f48a4f5 3457 description => "Extend volume size.",
0d02881c 3458 permissions => {
3b2773f6 3459 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3460 },
3461 parameters => {
3462 additionalProperties => 0,
2f48a4f5
DM
3463 properties => {
3464 node => get_standard_option('pve-node'),
335af808 3465 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3466 skiplock => get_standard_option('skiplock'),
3467 disk => {
3468 type => 'string',
3469 description => "The disk you want to resize.",
74479ee9 3470 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3471 },
3472 size => {
3473 type => 'string',
f91b2e45 3474 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3475 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
3476 },
3477 digest => {
3478 type => 'string',
3479 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3480 maxLength => 40,
3481 optional => 1,
3482 },
3483 },
0d02881c
AD
3484 },
3485 returns => { type => 'null'},
3486 code => sub {
3487 my ($param) = @_;
3488
3489 my $rpcenv = PVE::RPCEnvironment::get();
3490
3491 my $authuser = $rpcenv->get_user();
3492
3493 my $node = extract_param($param, 'node');
3494
3495 my $vmid = extract_param($param, 'vmid');
3496
3497 my $digest = extract_param($param, 'digest');
3498
2f48a4f5 3499 my $disk = extract_param($param, 'disk');
75466c4f 3500
2f48a4f5 3501 my $sizestr = extract_param($param, 'size');
0d02881c 3502
f91b2e45 3503 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3504 raise_param_exc({ skiplock => "Only root may use this option." })
3505 if $skiplock && $authuser ne 'root@pam';
3506
0d02881c
AD
3507 my $storecfg = PVE::Storage::config();
3508
0d02881c
AD
3509 my $updatefn = sub {
3510
ffda963f 3511 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3512
3513 die "checksum missmatch (file change by other user?)\n"
3514 if $digest && $digest ne $conf->{digest};
ffda963f 3515 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3516
f91b2e45
DM
3517 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3518
3519 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3520
d662790a
WL
3521 my (undef, undef, undef, undef, undef, undef, $format) =
3522 PVE::Storage::parse_volname($storecfg, $drive->{file});
3523
c2ed338e 3524 die "can't resize volume: $disk if snapshot exists\n"
d662790a
WL
3525 if %{$conf->{snapshots}} && $format eq 'qcow2';
3526
f91b2e45
DM
3527 my $volid = $drive->{file};
3528
3529 die "disk '$disk' has no associated volume\n" if !$volid;
3530
3531 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3532
3533 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3534
3535 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3536
b572a606 3537 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3538 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3539
ed94b2ad 3540 die "Could not determine current size of volume '$volid'\n" if !defined($size);
f8b829aa 3541
f91b2e45
DM
3542 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3543 my ($ext, $newsize, $unit) = ($1, $2, $4);
3544 if ($unit) {
3545 if ($unit eq 'K') {
3546 $newsize = $newsize * 1024;
3547 } elsif ($unit eq 'M') {
3548 $newsize = $newsize * 1024 * 1024;
3549 } elsif ($unit eq 'G') {
3550 $newsize = $newsize * 1024 * 1024 * 1024;
3551 } elsif ($unit eq 'T') {
3552 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3553 }
3554 }
3555 $newsize += $size if $ext;
3556 $newsize = int($newsize);
3557
9a478b17 3558 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3559
3560 return if $size == $newsize;
3561
2f48a4f5 3562 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3563
f91b2e45 3564 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3565
f91b2e45
DM
3566 $drive->{size} = $newsize;
3567 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3568
ffda963f 3569 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3570 };
0d02881c 3571
ffda963f 3572 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3573 return undef;
3574 }});
3575
9dbd1ee4 3576__PACKAGE__->register_method({
7e7d7b61 3577 name => 'snapshot_list',
9dbd1ee4 3578 path => '{vmid}/snapshot',
7e7d7b61
DM
3579 method => 'GET',
3580 description => "List all snapshots.",
3581 permissions => {
3582 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3583 },
3584 proxyto => 'node',
3585 protected => 1, # qemu pid files are only readable by root
3586 parameters => {
3587 additionalProperties => 0,
3588 properties => {
e261de40 3589 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3590 node => get_standard_option('pve-node'),
3591 },
3592 },
3593 returns => {
3594 type => 'array',
3595 items => {
3596 type => "object",
ce9b0a38
DM
3597 properties => {
3598 name => {
3599 description => "Snapshot identifier. Value 'current' identifies the current VM.",
3600 type => 'string',
3601 },
3602 vmstate => {
3603 description => "Snapshot includes RAM.",
3604 type => 'boolean',
3605 optional => 1,
3606 },
3607 description => {
3608 description => "Snapshot description.",
3609 type => 'string',
3610 },
3611 snaptime => {
3612 description => "Snapshot creation time",
3613 type => 'integer',
3614 renderer => 'timestamp',
3615 optional => 1,
3616 },
3617 parent => {
3618 description => "Parent snapshot identifier.",
3619 type => 'string',
3620 optional => 1,
3621 },
3622 },
7e7d7b61
DM
3623 },
3624 links => [ { rel => 'child', href => "{name}" } ],
3625 },
3626 code => sub {
3627 my ($param) = @_;
3628
6aa4651b
DM
3629 my $vmid = $param->{vmid};
3630
ffda963f 3631 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3632 my $snaphash = $conf->{snapshots} || {};
3633
3634 my $res = [];
3635
3636 foreach my $name (keys %$snaphash) {
0ea6bc69 3637 my $d = $snaphash->{$name};
75466c4f
DM
3638 my $item = {
3639 name => $name,
3640 snaptime => $d->{snaptime} || 0,
6aa4651b 3641 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3642 description => $d->{description} || '',
3643 };
0ea6bc69 3644 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3645 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3646 push @$res, $item;
3647 }
3648
6aa4651b 3649 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
ce9b0a38
DM
3650 my $current = {
3651 name => 'current',
3652 digest => $conf->{digest},
3653 running => $running,
3654 description => "You are here!",
3655 };
d1914468
DM
3656 $current->{parent} = $conf->{parent} if $conf->{parent};
3657
3658 push @$res, $current;
7e7d7b61
DM
3659
3660 return $res;
3661 }});
3662
3663__PACKAGE__->register_method({
3664 name => 'snapshot',
3665 path => '{vmid}/snapshot',
3666 method => 'POST',
9dbd1ee4
AD
3667 protected => 1,
3668 proxyto => 'node',
3669 description => "Snapshot a VM.",
3670 permissions => {
f1baf1df 3671 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3672 },
3673 parameters => {
3674 additionalProperties => 0,
3675 properties => {
3676 node => get_standard_option('pve-node'),
335af808 3677 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3678 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3679 vmstate => {
3680 optional => 1,
3681 type => 'boolean',
3682 description => "Save the vmstate",
3683 },
782f4f75
DM
3684 description => {
3685 optional => 1,
3686 type => 'string',
3687 description => "A textual description or comment.",
3688 },
9dbd1ee4
AD
3689 },
3690 },
7e7d7b61
DM
3691 returns => {
3692 type => 'string',
3693 description => "the task ID.",
3694 },
9dbd1ee4
AD
3695 code => sub {
3696 my ($param) = @_;
3697
3698 my $rpcenv = PVE::RPCEnvironment::get();
3699
3700 my $authuser = $rpcenv->get_user();
3701
3702 my $node = extract_param($param, 'node');
3703
3704 my $vmid = extract_param($param, 'vmid');
3705
9dbd1ee4
AD
3706 my $snapname = extract_param($param, 'snapname');
3707
d1914468
DM
3708 die "unable to use snapshot name 'current' (reserved name)\n"
3709 if $snapname eq 'current';
3710
7e7d7b61 3711 my $realcmd = sub {
22c377f0 3712 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
c2ed338e 3713 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3714 $param->{description});
7e7d7b61
DM
3715 };
3716
3717 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3718 }});
3719
154ccdcd
DM
3720__PACKAGE__->register_method({
3721 name => 'snapshot_cmd_idx',
3722 path => '{vmid}/snapshot/{snapname}',
3723 description => '',
3724 method => 'GET',
3725 permissions => {
3726 user => 'all',
3727 },
3728 parameters => {
3729 additionalProperties => 0,
3730 properties => {
3731 vmid => get_standard_option('pve-vmid'),
3732 node => get_standard_option('pve-node'),
8abd398b 3733 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3734 },
3735 },
3736 returns => {
3737 type => 'array',
3738 items => {
3739 type => "object",
3740 properties => {},
3741 },
3742 links => [ { rel => 'child', href => "{cmd}" } ],
3743 },
3744 code => sub {
3745 my ($param) = @_;
3746
3747 my $res = [];
3748
3749 push @$res, { cmd => 'rollback' };
d788cea6 3750 push @$res, { cmd => 'config' };
154ccdcd
DM
3751
3752 return $res;
3753 }});
3754
d788cea6
DM
3755__PACKAGE__->register_method({
3756 name => 'update_snapshot_config',
3757 path => '{vmid}/snapshot/{snapname}/config',
3758 method => 'PUT',
3759 protected => 1,
3760 proxyto => 'node',
3761 description => "Update snapshot metadata.",
3762 permissions => {
3763 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3764 },
3765 parameters => {
3766 additionalProperties => 0,
3767 properties => {
3768 node => get_standard_option('pve-node'),
3769 vmid => get_standard_option('pve-vmid'),
3770 snapname => get_standard_option('pve-snapshot-name'),
3771 description => {
3772 optional => 1,
3773 type => 'string',
3774 description => "A textual description or comment.",
3775 },
3776 },
3777 },
3778 returns => { type => 'null' },
3779 code => sub {
3780 my ($param) = @_;
3781
3782 my $rpcenv = PVE::RPCEnvironment::get();
3783
3784 my $authuser = $rpcenv->get_user();
3785
3786 my $vmid = extract_param($param, 'vmid');
3787
3788 my $snapname = extract_param($param, 'snapname');
3789
3790 return undef if !defined($param->{description});
3791
3792 my $updatefn = sub {
3793
ffda963f 3794 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3795
ffda963f 3796 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3797
3798 my $snap = $conf->{snapshots}->{$snapname};
3799
75466c4f
DM
3800 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3801
d788cea6
DM
3802 $snap->{description} = $param->{description} if defined($param->{description});
3803
ffda963f 3804 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3805 };
3806
ffda963f 3807 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3808
3809 return undef;
3810 }});
3811
3812__PACKAGE__->register_method({
3813 name => 'get_snapshot_config',
3814 path => '{vmid}/snapshot/{snapname}/config',
3815 method => 'GET',
3816 proxyto => 'node',
3817 description => "Get snapshot configuration",
3818 permissions => {
c268337d 3819 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
d788cea6
DM
3820 },
3821 parameters => {
3822 additionalProperties => 0,
3823 properties => {
3824 node => get_standard_option('pve-node'),
3825 vmid => get_standard_option('pve-vmid'),
3826 snapname => get_standard_option('pve-snapshot-name'),
3827 },
3828 },
3829 returns => { type => "object" },
3830 code => sub {
3831 my ($param) = @_;
3832
3833 my $rpcenv = PVE::RPCEnvironment::get();
3834
3835 my $authuser = $rpcenv->get_user();
3836
3837 my $vmid = extract_param($param, 'vmid');
3838
3839 my $snapname = extract_param($param, 'snapname');
3840
ffda963f 3841 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3842
3843 my $snap = $conf->{snapshots}->{$snapname};
3844
75466c4f
DM
3845 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3846
d788cea6
DM
3847 return $snap;
3848 }});
3849
7e7d7b61
DM
3850__PACKAGE__->register_method({
3851 name => 'rollback',
154ccdcd 3852 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3853 method => 'POST',
3854 protected => 1,
3855 proxyto => 'node',
3856 description => "Rollback VM state to specified snapshot.",
3857 permissions => {
c268337d 3858 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
7e7d7b61
DM
3859 },
3860 parameters => {
3861 additionalProperties => 0,
3862 properties => {
3863 node => get_standard_option('pve-node'),
335af808 3864 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3865 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3866 },
3867 },
3868 returns => {
3869 type => 'string',
3870 description => "the task ID.",
3871 },
3872 code => sub {
3873 my ($param) = @_;
3874
3875 my $rpcenv = PVE::RPCEnvironment::get();
3876
3877 my $authuser = $rpcenv->get_user();
3878
3879 my $node = extract_param($param, 'node');
3880
3881 my $vmid = extract_param($param, 'vmid');
3882
3883 my $snapname = extract_param($param, 'snapname');
3884
7e7d7b61 3885 my $realcmd = sub {
22c377f0 3886 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3887 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3888 };
3889
c068c1c3
WL
3890 my $worker = sub {
3891 # hold migration lock, this makes sure that nobody create replication snapshots
3892 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3893 };
3894
3895 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3896 }});
3897
3898__PACKAGE__->register_method({
3899 name => 'delsnapshot',
3900 path => '{vmid}/snapshot/{snapname}',
3901 method => 'DELETE',
3902 protected => 1,
3903 proxyto => 'node',
3904 description => "Delete a VM snapshot.",
3905 permissions => {
f1baf1df 3906 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3907 },
3908 parameters => {
3909 additionalProperties => 0,
3910 properties => {
3911 node => get_standard_option('pve-node'),
335af808 3912 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3913 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3914 force => {
3915 optional => 1,
3916 type => 'boolean',
3917 description => "For removal from config file, even if removing disk snapshots fails.",
3918 },
7e7d7b61
DM
3919 },
3920 },
3921 returns => {
3922 type => 'string',
3923 description => "the task ID.",
3924 },
3925 code => sub {
3926 my ($param) = @_;
3927
3928 my $rpcenv = PVE::RPCEnvironment::get();
3929
3930 my $authuser = $rpcenv->get_user();
3931
3932 my $node = extract_param($param, 'node');
3933
3934 my $vmid = extract_param($param, 'vmid');
3935
3936 my $snapname = extract_param($param, 'snapname');
3937
7e7d7b61 3938 my $realcmd = sub {
22c377f0 3939 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3940 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3941 };
9dbd1ee4 3942
7b2257a8 3943 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3944 }});
3945
04a69bb4
AD
3946__PACKAGE__->register_method({
3947 name => 'template',
3948 path => '{vmid}/template',
3949 method => 'POST',
3950 protected => 1,
3951 proxyto => 'node',
3952 description => "Create a Template.",
b02691d8 3953 permissions => {
7af0a6c8
DM
3954 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3955 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3956 },
04a69bb4
AD
3957 parameters => {
3958 additionalProperties => 0,
3959 properties => {
3960 node => get_standard_option('pve-node'),
335af808 3961 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3962 disk => {
3963 optional => 1,
3964 type => 'string',
3965 description => "If you want to convert only 1 disk to base image.",
74479ee9 3966 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3967 },
3968
3969 },
3970 },
3971 returns => { type => 'null'},
3972 code => sub {
3973 my ($param) = @_;
3974
3975 my $rpcenv = PVE::RPCEnvironment::get();
3976
3977 my $authuser = $rpcenv->get_user();
3978
3979 my $node = extract_param($param, 'node');
3980
3981 my $vmid = extract_param($param, 'vmid');
3982
3983 my $disk = extract_param($param, 'disk');
3984
3985 my $updatefn = sub {
3986
ffda963f 3987 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3988
ffda963f 3989 PVE::QemuConfig->check_lock($conf);
04a69bb4 3990
75466c4f 3991 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3992 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3993
75466c4f 3994 die "you can't convert a template to a template\n"
ffda963f 3995 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3996
75466c4f 3997 die "you can't convert a VM to template if VM is running\n"
218cab9a 3998 if PVE::QemuServer::check_running($vmid);
35c5fdef 3999
04a69bb4
AD
4000 my $realcmd = sub {
4001 PVE::QemuServer::template_create($vmid, $conf, $disk);
4002 };
04a69bb4 4003
75e7e997 4004 $conf->{template} = 1;
ffda963f 4005 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
4006
4007 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
4008 };
4009
ffda963f 4010 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
4011 return undef;
4012 }});
4013
73709749
ML
4014__PACKAGE__->register_method({
4015 name => 'cloudinit_generated_config_dump',
4016 path => '{vmid}/cloudinit/dump',
4017 method => 'GET',
4018 proxyto => 'node',
4019 description => "Get automatically generated cloudinit config.",
4020 permissions => {
4021 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4022 },
4023 parameters => {
4024 additionalProperties => 0,
4025 properties => {
4026 node => get_standard_option('pve-node'),
4027 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
4028 type => {
4029 description => 'Config type.',
4030 type => 'string',
4031 enum => ['user', 'network', 'meta'],
4032 },
4033 },
4034 },
4035 returns => {
4036 type => 'string',
4037 },
4038 code => sub {
4039 my ($param) = @_;
4040
4041 my $conf = PVE::QemuConfig->load_config($param->{vmid});
4042
4043 return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{vmid}, $param->{type});
4044 }});
4045
1e3baf05 40461;