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