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