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