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