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