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