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