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