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