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