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