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