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