]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
create_disks: refactor out and improve efidisk creation
[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 {
2700 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
2701
9dbf9b54
FG
2702 warn "moving disk with snapshots, snapshots will not be moved!\n"
2703 if $snapshotted;
2704
e2cd75fa
DM
2705 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2706 $vmid, $storeid, $format, 1, $newvollist);
2707
2708 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2709
8793d495 2710 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2711
fbd7dcce
FG
2712 # convert moved disk to base if part of template
2713 PVE::QemuServer::template_create($vmid, $conf, $disk)
2714 if PVE::QemuConfig->is_template($conf);
2715
ffda963f 2716 PVE::QemuConfig->write_config($vmid, $conf);
73272365 2717
f34ebd52 2718 eval {
73272365 2719 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 2720 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
2721 if !$running;
2722 };
2723 warn $@ if $@;
586bfa78
AD
2724 };
2725 if (my $err = $@) {
2726
2727 foreach my $volid (@$newvollist) {
2728 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2729 warn $@ if $@;
2730 }
2731 die "storage migration failed: $err";
2732 }
70d45e33
DM
2733
2734 if ($param->{delete}) {
a3d0bafb
FG
2735 eval {
2736 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
2737 PVE::Storage::vdisk_free($storecfg, $old_volid);
2738 };
2739 warn $@ if $@;
70d45e33 2740 }
586bfa78
AD
2741 };
2742
2743 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2744 };
e2cd75fa 2745
ffda963f 2746 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
2747 }});
2748
3ea94c60 2749__PACKAGE__->register_method({
afdb31d5 2750 name => 'migrate_vm',
3ea94c60
DM
2751 path => '{vmid}/migrate',
2752 method => 'POST',
2753 protected => 1,
2754 proxyto => 'node',
2755 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2756 permissions => {
2757 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2758 },
3ea94c60
DM
2759 parameters => {
2760 additionalProperties => 0,
2761 properties => {
2762 node => get_standard_option('pve-node'),
335af808
DM
2763 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2764 target => get_standard_option('pve-node', {
2765 description => "Target node.",
2766 completion => \&PVE::Cluster::complete_migration_target,
2767 }),
3ea94c60
DM
2768 online => {
2769 type => 'boolean',
2770 description => "Use online/live migration.",
2771 optional => 1,
2772 },
2773 force => {
2774 type => 'boolean',
2775 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2776 optional => 1,
2777 },
2de2d6f7
TL
2778 migration_type => {
2779 type => 'string',
2780 enum => ['secure', 'insecure'],
c07a9e3d 2781 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
2782 optional => 1,
2783 },
2784 migration_network => {
c07a9e3d 2785 type => 'string', format => 'CIDR',
2de2d6f7
TL
2786 description => "CIDR of the (sub) network that is used for migration.",
2787 optional => 1,
2788 },
56af7146
AD
2789 "with-local-disks" => {
2790 type => 'boolean',
2791 description => "Enable live storage migration for local disk",
b74cad8a 2792 optional => 1,
56af7146
AD
2793 },
2794 targetstorage => get_standard_option('pve-storage-id', {
2795 description => "Default target storage.",
2796 optional => 1,
2797 completion => \&PVE::QemuServer::complete_storage,
2798 }),
3ea94c60
DM
2799 },
2800 },
afdb31d5 2801 returns => {
3ea94c60
DM
2802 type => 'string',
2803 description => "the task ID.",
2804 },
2805 code => sub {
2806 my ($param) = @_;
2807
2808 my $rpcenv = PVE::RPCEnvironment::get();
2809
a0d1b1a2 2810 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2811
2812 my $target = extract_param($param, 'target');
2813
2814 my $localnode = PVE::INotify::nodename();
2815 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2816
2817 PVE::Cluster::check_cfs_quorum();
2818
2819 PVE::Cluster::check_node_exists($target);
2820
2821 my $targetip = PVE::Cluster::remote_node_ip($target);
2822
2823 my $vmid = extract_param($param, 'vmid');
2824
bd2d5fe6 2825 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
2826 if !$param->{online} && $param->{targetstorage};
2827
afdb31d5 2828 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2829 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 2830
2de2d6f7
TL
2831 raise_param_exc({ migration_type => "Only root may use this option." })
2832 if $param->{migration_type} && $authuser ne 'root@pam';
2833
2834 # allow root only until better network permissions are available
2835 raise_param_exc({ migration_network => "Only root may use this option." })
2836 if $param->{migration_network} && $authuser ne 'root@pam';
2837
3ea94c60 2838 # test if VM exists
ffda963f 2839 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
2840
2841 # try to detect errors early
a5ed42d3 2842
ffda963f 2843 PVE::QemuConfig->check_lock($conf);
a5ed42d3 2844
3ea94c60 2845 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2846 die "cant migrate running VM without --online\n"
3ea94c60
DM
2847 if !$param->{online};
2848 }
2849
47152e2e 2850 my $storecfg = PVE::Storage::config();
d80ad67f
AD
2851
2852 if( $param->{targetstorage}) {
2853 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
2854 } else {
2855 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
2856 }
47152e2e 2857
2003f0f8 2858 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2859
88fc87b4
DM
2860 my $hacmd = sub {
2861 my $upid = shift;
3ea94c60 2862
c44291cd 2863 my $service = "vm:$vmid";
88fc87b4 2864
2003f0f8 2865 my $cmd = ['ha-manager', 'migrate', $service, $target];
88fc87b4 2866
02765844 2867 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4
DM
2868
2869 PVE::Tools::run_command($cmd);
2870
2871 return;
2872 };
2873
2874 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2875
2876 } else {
2877
f53c6ad8 2878 my $realcmd = sub {
f53c6ad8
DM
2879 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2880 };
88fc87b4 2881
f53c6ad8
DM
2882 my $worker = sub {
2883 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
2884 };
2885
f53c6ad8 2886 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 2887 }
3ea94c60 2888
3ea94c60 2889 }});
1e3baf05 2890
91c94f0a 2891__PACKAGE__->register_method({
afdb31d5
DM
2892 name => 'monitor',
2893 path => '{vmid}/monitor',
91c94f0a
DM
2894 method => 'POST',
2895 protected => 1,
2896 proxyto => 'node',
2897 description => "Execute Qemu monitor commands.",
a0d1b1a2 2898 permissions => {
a8f2f427 2899 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 2900 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 2901 },
91c94f0a
DM
2902 parameters => {
2903 additionalProperties => 0,
2904 properties => {
2905 node => get_standard_option('pve-node'),
2906 vmid => get_standard_option('pve-vmid'),
2907 command => {
2908 type => 'string',
2909 description => "The monitor command.",
2910 }
2911 },
2912 },
2913 returns => { type => 'string'},
2914 code => sub {
2915 my ($param) = @_;
2916
a8f2f427
FG
2917 my $rpcenv = PVE::RPCEnvironment::get();
2918 my $authuser = $rpcenv->get_user();
2919
2920 my $is_ro = sub {
2921 my $command = shift;
2922 return $command =~ m/^\s*info(\s+|$)/
2923 || $command =~ m/^\s*help\s*$/;
2924 };
2925
2926 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2927 if !&$is_ro($param->{command});
2928
91c94f0a
DM
2929 my $vmid = $param->{vmid};
2930
ffda963f 2931 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
2932
2933 my $res = '';
2934 eval {
7b7c6d1b 2935 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2936 };
2937 $res = "ERROR: $@" if $@;
2938
2939 return $res;
2940 }});
2941
a5d5341c 2942my $guest_agent_commands = [
249d8fed
DM
2943 'ping',
2944 'get-time',
2945 'info',
2946 'fsfreeze-status',
2947 'fsfreeze-freeze',
2948 'fsfreeze-thaw',
2949 'fstrim',
2950 'network-get-interfaces',
2951 'get-vcpus',
2952 'get-fsinfo',
2953 'get-memory-blocks',
2954 'get-memory-block-info',
2955 'suspend-hybrid',
2956 'suspend-ram',
2957 'suspend-disk',
2958 'shutdown',
a5d5341c
DM
2959 ];
2960
d1a47427
WL
2961__PACKAGE__->register_method({
2962 name => 'agent',
2963 path => '{vmid}/agent',
2964 method => 'POST',
2965 protected => 1,
2966 proxyto => 'node',
2967 description => "Execute Qemu Guest Agent commands.",
2968 permissions => {
2969 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2970 },
2971 parameters => {
2972 additionalProperties => 0,
2973 properties => {
2974 node => get_standard_option('pve-node'),
f38c5e27
DM
2975 vmid => get_standard_option('pve-vmid', {
2976 completion => \&PVE::QemuServer::complete_vmid_running }),
d1a47427
WL
2977 command => {
2978 type => 'string',
2979 description => "The QGA command.",
a5d5341c 2980 enum => $guest_agent_commands,
c07a9e3d 2981 },
d1a47427
WL
2982 },
2983 },
57bdd459
DM
2984 returns => {
2985 type => 'object',
2986 description => "Returns an object with a single `result` property. The type of that
2987property depends on the executed command.",
2988 },
d1a47427
WL
2989 code => sub {
2990 my ($param) = @_;
2991
2992 my $vmid = $param->{vmid};
2993
2994 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
2995
d1a47427
WL
2996 die "No Qemu Guest Agent\n" if !defined($conf->{agent});
2997 die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
2998
249d8fed
DM
2999 my $cmd = $param->{command};
3000
3001 my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
d1a47427 3002
57bdd459 3003 return { result => $res };
d1a47427
WL
3004 }});
3005
0d02881c
AD
3006__PACKAGE__->register_method({
3007 name => 'resize_vm',
614e3941 3008 path => '{vmid}/resize',
0d02881c
AD
3009 method => 'PUT',
3010 protected => 1,
3011 proxyto => 'node',
2f48a4f5 3012 description => "Extend volume size.",
0d02881c 3013 permissions => {
3b2773f6 3014 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3015 },
3016 parameters => {
3017 additionalProperties => 0,
2f48a4f5
DM
3018 properties => {
3019 node => get_standard_option('pve-node'),
335af808 3020 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3021 skiplock => get_standard_option('skiplock'),
3022 disk => {
3023 type => 'string',
3024 description => "The disk you want to resize.",
74479ee9 3025 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3026 },
3027 size => {
3028 type => 'string',
f91b2e45 3029 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3030 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
3031 },
3032 digest => {
3033 type => 'string',
3034 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3035 maxLength => 40,
3036 optional => 1,
3037 },
3038 },
0d02881c
AD
3039 },
3040 returns => { type => 'null'},
3041 code => sub {
3042 my ($param) = @_;
3043
3044 my $rpcenv = PVE::RPCEnvironment::get();
3045
3046 my $authuser = $rpcenv->get_user();
3047
3048 my $node = extract_param($param, 'node');
3049
3050 my $vmid = extract_param($param, 'vmid');
3051
3052 my $digest = extract_param($param, 'digest');
3053
2f48a4f5 3054 my $disk = extract_param($param, 'disk');
75466c4f 3055
2f48a4f5 3056 my $sizestr = extract_param($param, 'size');
0d02881c 3057
f91b2e45 3058 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3059 raise_param_exc({ skiplock => "Only root may use this option." })
3060 if $skiplock && $authuser ne 'root@pam';
3061
0d02881c
AD
3062 my $storecfg = PVE::Storage::config();
3063
0d02881c
AD
3064 my $updatefn = sub {
3065
ffda963f 3066 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3067
3068 die "checksum missmatch (file change by other user?)\n"
3069 if $digest && $digest ne $conf->{digest};
ffda963f 3070 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3071
f91b2e45
DM
3072 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3073
3074 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3075
d662790a
WL
3076 my (undef, undef, undef, undef, undef, undef, $format) =
3077 PVE::Storage::parse_volname($storecfg, $drive->{file});
3078
3079 die "can't resize volume: $disk if snapshot exists\n"
3080 if %{$conf->{snapshots}} && $format eq 'qcow2';
3081
f91b2e45
DM
3082 my $volid = $drive->{file};
3083
3084 die "disk '$disk' has no associated volume\n" if !$volid;
3085
3086 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3087
3088 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3089
3090 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3091
b572a606 3092 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3093 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3094
3095 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3096 my ($ext, $newsize, $unit) = ($1, $2, $4);
3097 if ($unit) {
3098 if ($unit eq 'K') {
3099 $newsize = $newsize * 1024;
3100 } elsif ($unit eq 'M') {
3101 $newsize = $newsize * 1024 * 1024;
3102 } elsif ($unit eq 'G') {
3103 $newsize = $newsize * 1024 * 1024 * 1024;
3104 } elsif ($unit eq 'T') {
3105 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3106 }
3107 }
3108 $newsize += $size if $ext;
3109 $newsize = int($newsize);
3110
9a478b17 3111 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3112
3113 return if $size == $newsize;
3114
2f48a4f5 3115 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3116
f91b2e45 3117 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3118
f91b2e45
DM
3119 $drive->{size} = $newsize;
3120 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3121
ffda963f 3122 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3123 };
0d02881c 3124
ffda963f 3125 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3126 return undef;
3127 }});
3128
9dbd1ee4 3129__PACKAGE__->register_method({
7e7d7b61 3130 name => 'snapshot_list',
9dbd1ee4 3131 path => '{vmid}/snapshot',
7e7d7b61
DM
3132 method => 'GET',
3133 description => "List all snapshots.",
3134 permissions => {
3135 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3136 },
3137 proxyto => 'node',
3138 protected => 1, # qemu pid files are only readable by root
3139 parameters => {
3140 additionalProperties => 0,
3141 properties => {
e261de40 3142 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3143 node => get_standard_option('pve-node'),
3144 },
3145 },
3146 returns => {
3147 type => 'array',
3148 items => {
3149 type => "object",
3150 properties => {},
3151 },
3152 links => [ { rel => 'child', href => "{name}" } ],
3153 },
3154 code => sub {
3155 my ($param) = @_;
3156
6aa4651b
DM
3157 my $vmid = $param->{vmid};
3158
ffda963f 3159 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3160 my $snaphash = $conf->{snapshots} || {};
3161
3162 my $res = [];
3163
3164 foreach my $name (keys %$snaphash) {
0ea6bc69 3165 my $d = $snaphash->{$name};
75466c4f
DM
3166 my $item = {
3167 name => $name,
3168 snaptime => $d->{snaptime} || 0,
6aa4651b 3169 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3170 description => $d->{description} || '',
3171 };
0ea6bc69 3172 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3173 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3174 push @$res, $item;
3175 }
3176
6aa4651b
DM
3177 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
3178 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
3179 $current->{parent} = $conf->{parent} if $conf->{parent};
3180
3181 push @$res, $current;
7e7d7b61
DM
3182
3183 return $res;
3184 }});
3185
3186__PACKAGE__->register_method({
3187 name => 'snapshot',
3188 path => '{vmid}/snapshot',
3189 method => 'POST',
9dbd1ee4
AD
3190 protected => 1,
3191 proxyto => 'node',
3192 description => "Snapshot a VM.",
3193 permissions => {
f1baf1df 3194 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3195 },
3196 parameters => {
3197 additionalProperties => 0,
3198 properties => {
3199 node => get_standard_option('pve-node'),
335af808 3200 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3201 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3202 vmstate => {
3203 optional => 1,
3204 type => 'boolean',
3205 description => "Save the vmstate",
3206 },
782f4f75
DM
3207 description => {
3208 optional => 1,
3209 type => 'string',
3210 description => "A textual description or comment.",
3211 },
9dbd1ee4
AD
3212 },
3213 },
7e7d7b61
DM
3214 returns => {
3215 type => 'string',
3216 description => "the task ID.",
3217 },
9dbd1ee4
AD
3218 code => sub {
3219 my ($param) = @_;
3220
3221 my $rpcenv = PVE::RPCEnvironment::get();
3222
3223 my $authuser = $rpcenv->get_user();
3224
3225 my $node = extract_param($param, 'node');
3226
3227 my $vmid = extract_param($param, 'vmid');
3228
9dbd1ee4
AD
3229 my $snapname = extract_param($param, 'snapname');
3230
d1914468
DM
3231 die "unable to use snapshot name 'current' (reserved name)\n"
3232 if $snapname eq 'current';
3233
7e7d7b61 3234 my $realcmd = sub {
22c377f0 3235 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
b2c9558d 3236 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3237 $param->{description});
7e7d7b61
DM
3238 };
3239
3240 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3241 }});
3242
154ccdcd
DM
3243__PACKAGE__->register_method({
3244 name => 'snapshot_cmd_idx',
3245 path => '{vmid}/snapshot/{snapname}',
3246 description => '',
3247 method => 'GET',
3248 permissions => {
3249 user => 'all',
3250 },
3251 parameters => {
3252 additionalProperties => 0,
3253 properties => {
3254 vmid => get_standard_option('pve-vmid'),
3255 node => get_standard_option('pve-node'),
8abd398b 3256 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3257 },
3258 },
3259 returns => {
3260 type => 'array',
3261 items => {
3262 type => "object",
3263 properties => {},
3264 },
3265 links => [ { rel => 'child', href => "{cmd}" } ],
3266 },
3267 code => sub {
3268 my ($param) = @_;
3269
3270 my $res = [];
3271
3272 push @$res, { cmd => 'rollback' };
d788cea6 3273 push @$res, { cmd => 'config' };
154ccdcd
DM
3274
3275 return $res;
3276 }});
3277
d788cea6
DM
3278__PACKAGE__->register_method({
3279 name => 'update_snapshot_config',
3280 path => '{vmid}/snapshot/{snapname}/config',
3281 method => 'PUT',
3282 protected => 1,
3283 proxyto => 'node',
3284 description => "Update snapshot metadata.",
3285 permissions => {
3286 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3287 },
3288 parameters => {
3289 additionalProperties => 0,
3290 properties => {
3291 node => get_standard_option('pve-node'),
3292 vmid => get_standard_option('pve-vmid'),
3293 snapname => get_standard_option('pve-snapshot-name'),
3294 description => {
3295 optional => 1,
3296 type => 'string',
3297 description => "A textual description or comment.",
3298 },
3299 },
3300 },
3301 returns => { type => 'null' },
3302 code => sub {
3303 my ($param) = @_;
3304
3305 my $rpcenv = PVE::RPCEnvironment::get();
3306
3307 my $authuser = $rpcenv->get_user();
3308
3309 my $vmid = extract_param($param, 'vmid');
3310
3311 my $snapname = extract_param($param, 'snapname');
3312
3313 return undef if !defined($param->{description});
3314
3315 my $updatefn = sub {
3316
ffda963f 3317 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3318
ffda963f 3319 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3320
3321 my $snap = $conf->{snapshots}->{$snapname};
3322
75466c4f
DM
3323 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3324
d788cea6
DM
3325 $snap->{description} = $param->{description} if defined($param->{description});
3326
ffda963f 3327 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3328 };
3329
ffda963f 3330 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3331
3332 return undef;
3333 }});
3334
3335__PACKAGE__->register_method({
3336 name => 'get_snapshot_config',
3337 path => '{vmid}/snapshot/{snapname}/config',
3338 method => 'GET',
3339 proxyto => 'node',
3340 description => "Get snapshot configuration",
3341 permissions => {
3342 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3343 },
3344 parameters => {
3345 additionalProperties => 0,
3346 properties => {
3347 node => get_standard_option('pve-node'),
3348 vmid => get_standard_option('pve-vmid'),
3349 snapname => get_standard_option('pve-snapshot-name'),
3350 },
3351 },
3352 returns => { type => "object" },
3353 code => sub {
3354 my ($param) = @_;
3355
3356 my $rpcenv = PVE::RPCEnvironment::get();
3357
3358 my $authuser = $rpcenv->get_user();
3359
3360 my $vmid = extract_param($param, 'vmid');
3361
3362 my $snapname = extract_param($param, 'snapname');
3363
ffda963f 3364 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3365
3366 my $snap = $conf->{snapshots}->{$snapname};
3367
75466c4f
DM
3368 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3369
d788cea6
DM
3370 return $snap;
3371 }});
3372
7e7d7b61
DM
3373__PACKAGE__->register_method({
3374 name => 'rollback',
154ccdcd 3375 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3376 method => 'POST',
3377 protected => 1,
3378 proxyto => 'node',
3379 description => "Rollback VM state to specified snapshot.",
3380 permissions => {
f1baf1df 3381 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3382 },
3383 parameters => {
3384 additionalProperties => 0,
3385 properties => {
3386 node => get_standard_option('pve-node'),
335af808 3387 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3388 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3389 },
3390 },
3391 returns => {
3392 type => 'string',
3393 description => "the task ID.",
3394 },
3395 code => sub {
3396 my ($param) = @_;
3397
3398 my $rpcenv = PVE::RPCEnvironment::get();
3399
3400 my $authuser = $rpcenv->get_user();
3401
3402 my $node = extract_param($param, 'node');
3403
3404 my $vmid = extract_param($param, 'vmid');
3405
3406 my $snapname = extract_param($param, 'snapname');
3407
7e7d7b61 3408 my $realcmd = sub {
22c377f0 3409 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3410 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3411 };
3412
c068c1c3
WL
3413 my $worker = sub {
3414 # hold migration lock, this makes sure that nobody create replication snapshots
3415 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3416 };
3417
3418 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3419 }});
3420
3421__PACKAGE__->register_method({
3422 name => 'delsnapshot',
3423 path => '{vmid}/snapshot/{snapname}',
3424 method => 'DELETE',
3425 protected => 1,
3426 proxyto => 'node',
3427 description => "Delete a VM snapshot.",
3428 permissions => {
f1baf1df 3429 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3430 },
3431 parameters => {
3432 additionalProperties => 0,
3433 properties => {
3434 node => get_standard_option('pve-node'),
335af808 3435 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3436 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3437 force => {
3438 optional => 1,
3439 type => 'boolean',
3440 description => "For removal from config file, even if removing disk snapshots fails.",
3441 },
7e7d7b61
DM
3442 },
3443 },
3444 returns => {
3445 type => 'string',
3446 description => "the task ID.",
3447 },
3448 code => sub {
3449 my ($param) = @_;
3450
3451 my $rpcenv = PVE::RPCEnvironment::get();
3452
3453 my $authuser = $rpcenv->get_user();
3454
3455 my $node = extract_param($param, 'node');
3456
3457 my $vmid = extract_param($param, 'vmid');
3458
3459 my $snapname = extract_param($param, 'snapname');
3460
7e7d7b61 3461 my $realcmd = sub {
22c377f0 3462 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3463 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3464 };
9dbd1ee4 3465
7b2257a8 3466 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3467 }});
3468
04a69bb4
AD
3469__PACKAGE__->register_method({
3470 name => 'template',
3471 path => '{vmid}/template',
3472 method => 'POST',
3473 protected => 1,
3474 proxyto => 'node',
3475 description => "Create a Template.",
b02691d8 3476 permissions => {
7af0a6c8
DM
3477 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3478 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3479 },
04a69bb4
AD
3480 parameters => {
3481 additionalProperties => 0,
3482 properties => {
3483 node => get_standard_option('pve-node'),
335af808 3484 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3485 disk => {
3486 optional => 1,
3487 type => 'string',
3488 description => "If you want to convert only 1 disk to base image.",
74479ee9 3489 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3490 },
3491
3492 },
3493 },
3494 returns => { type => 'null'},
3495 code => sub {
3496 my ($param) = @_;
3497
3498 my $rpcenv = PVE::RPCEnvironment::get();
3499
3500 my $authuser = $rpcenv->get_user();
3501
3502 my $node = extract_param($param, 'node');
3503
3504 my $vmid = extract_param($param, 'vmid');
3505
3506 my $disk = extract_param($param, 'disk');
3507
3508 my $updatefn = sub {
3509
ffda963f 3510 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3511
ffda963f 3512 PVE::QemuConfig->check_lock($conf);
04a69bb4 3513
75466c4f 3514 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3515 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3516
75466c4f 3517 die "you can't convert a template to a template\n"
ffda963f 3518 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3519
75466c4f 3520 die "you can't convert a VM to template if VM is running\n"
218cab9a 3521 if PVE::QemuServer::check_running($vmid);
35c5fdef 3522
04a69bb4
AD
3523 my $realcmd = sub {
3524 PVE::QemuServer::template_create($vmid, $conf, $disk);
3525 };
04a69bb4 3526
75e7e997 3527 $conf->{template} = 1;
ffda963f 3528 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3529
3530 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3531 };
3532
ffda963f 3533 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3534 return undef;
3535 }});
3536
1e3baf05 35371;