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