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