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