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