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