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