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