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