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