]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
allow disks on shared storages on replicated 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);
9b1396ed
WB
975 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
976 return if $scfg->{shared};
17677004
WB
977 die "cannot add non-replicatable volume to a replicated VM\n";
978 };
979
5555edea 980 foreach my $opt (keys %$param) {
74479ee9 981 if (PVE::QemuServer::is_valid_drivename($opt)) {
5555edea
DM
982 # cleanup drive path
983 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
f9091201 984 raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
5555edea 985 PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
17677004 986 $check_replication->($drive);
5555edea
DM
987 $param->{$opt} = PVE::QemuServer::print_drive($vmid, $drive);
988 } elsif ($opt =~ m/^net(\d+)$/) {
989 # add macaddr
990 my $net = PVE::QemuServer::parse_net($param->{$opt});
991 $param->{$opt} = PVE::QemuServer::print_net($net);
1e68cb19 992 }
5555edea 993 }
1e3baf05 994
5555edea 995 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
ae57f6b3 996
5555edea 997 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
ae57f6b3 998
5555edea 999 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1e3baf05 1000
5555edea 1001 my $updatefn = sub {
1e3baf05 1002
ffda963f 1003 my $conf = PVE::QemuConfig->load_config($vmid);
1e3baf05 1004
5555edea
DM
1005 die "checksum missmatch (file change by other user?)\n"
1006 if $digest && $digest ne $conf->{digest};
1007
ffda963f 1008 PVE::QemuConfig->check_lock($conf) if !$skiplock;
7043d946 1009
d3df8cf3
DM
1010 foreach my $opt (keys %$revert) {
1011 if (defined($conf->{$opt})) {
1012 $param->{$opt} = $conf->{$opt};
1013 } elsif (defined($conf->{pending}->{$opt})) {
1014 push @delete, $opt;
1015 }
1016 }
1017
5555edea 1018 if ($param->{memory} || defined($param->{balloon})) {
6ca8b698
DM
1019 my $maxmem = $param->{memory} || $conf->{pending}->{memory} || $conf->{memory} || $defaults->{memory};
1020 my $balloon = defined($param->{balloon}) ? $param->{balloon} : $conf->{pending}->{balloon} || $conf->{balloon};
7043d946 1021
5555edea
DM
1022 die "balloon value too large (must be smaller than assigned memory)\n"
1023 if $balloon && $balloon > $maxmem;
1024 }
1e3baf05 1025
5555edea 1026 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1e3baf05 1027
5555edea 1028 my $worker = sub {
7bfdeb5f 1029
5555edea 1030 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
c2a64aa7 1031
202d1f45
DM
1032 # write updates to pending section
1033
3a11fadb
DM
1034 my $modified = {}; # record what $option we modify
1035
202d1f45 1036 foreach my $opt (@delete) {
3a11fadb 1037 $modified->{$opt} = 1;
ffda963f 1038 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
af6d2db4 1039 if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
d2c6bf93
FG
1040 warn "cannot delete '$opt' - not set in current configuration!\n";
1041 $modified->{$opt} = 0;
1042 next;
1043 }
1044
202d1f45 1045 if ($opt =~ m/^unused/) {
202d1f45 1046 my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
ffda963f 1047 PVE::QemuConfig->check_protection($conf, "can't remove unused disk '$drive->{file}'");
4d8d55f1 1048 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
3dc38fbb
WB
1049 if (PVE::QemuServer::try_deallocate_drive($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1050 delete $conf->{$opt};
ffda963f 1051 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1052 }
74479ee9 1053 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
ffda963f 1054 PVE::QemuConfig->check_protection($conf, "can't remove drive '$opt'");
202d1f45 1055 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
055d554d 1056 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
202d1f45 1057 if defined($conf->{pending}->{$opt});
3dc38fbb 1058 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
ffda963f 1059 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1060 } else {
3dc38fbb 1061 PVE::QemuServer::vmconfig_delete_pending_option($conf, $opt, $force);
ffda963f 1062 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45 1063 }
5d39a182 1064 }
1e3baf05 1065
202d1f45 1066 foreach my $opt (keys %$param) { # add/change
3a11fadb 1067 $modified->{$opt} = 1;
ffda963f 1068 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
202d1f45
DM
1069 next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed
1070
74479ee9 1071 if (PVE::QemuServer::is_valid_drivename($opt)) {
202d1f45
DM
1072 my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
1073 if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
1074 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1075 } else {
1076 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1077 }
055d554d 1078 PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
202d1f45
DM
1079 if defined($conf->{pending}->{$opt});
1080
1081 &$create_disks($rpcenv, $authuser, $conf->{pending}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1082 } else {
1083 $conf->{pending}->{$opt} = $param->{$opt};
1084 }
055d554d 1085 PVE::QemuServer::vmconfig_undelete_pending_option($conf, $opt);
ffda963f 1086 PVE::QemuConfig->write_config($vmid, $conf);
202d1f45
DM
1087 }
1088
1089 # remove pending changes when nothing changed
ffda963f 1090 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
c750e90a 1091 my $changes = PVE::QemuServer::vmconfig_cleanup_pending($conf);
ffda963f 1092 PVE::QemuConfig->write_config($vmid, $conf) if $changes;
202d1f45
DM
1093
1094 return if !scalar(keys %{$conf->{pending}});
1095
7bfdeb5f 1096 my $running = PVE::QemuServer::check_running($vmid);
39001640
DM
1097
1098 # apply pending changes
1099
ffda963f 1100 $conf = PVE::QemuConfig->load_config($vmid); # update/reload
39001640 1101
3a11fadb
DM
1102 if ($running) {
1103 my $errors = {};
1104 PVE::QemuServer::vmconfig_hotplug_pending($vmid, $conf, $storecfg, $modified, $errors);
1105 raise_param_exc($errors) if scalar(keys %$errors);
1106 } else {
1107 PVE::QemuServer::vmconfig_apply_pending($vmid, $conf, $storecfg, $running);
1108 }
1e68cb19 1109
915d3481 1110 return;
5d39a182
DM
1111 };
1112
5555edea
DM
1113 if ($sync) {
1114 &$worker();
1115 return undef;
1116 } else {
1117 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
fcdb0117 1118
5555edea
DM
1119 if ($background_delay) {
1120
1121 # Note: It would be better to do that in the Event based HTTPServer
7043d946 1122 # to avoid blocking call to sleep.
5555edea
DM
1123
1124 my $end_time = time() + $background_delay;
1125
1126 my $task = PVE::Tools::upid_decode($upid);
1127
1128 my $running = 1;
1129 while (time() < $end_time) {
1130 $running = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
1131 last if !$running;
1132 sleep(1); # this gets interrupted when child process ends
1133 }
1134
1135 if (!$running) {
1136 my $status = PVE::Tools::upid_read_status($upid);
1137 return undef if $status eq 'OK';
1138 die $status;
1139 }
7043d946 1140 }
5555edea
DM
1141
1142 return $upid;
1143 }
1144 };
1145
ffda963f 1146 return PVE::QemuConfig->lock_config($vmid, $updatefn);
5555edea
DM
1147};
1148
1149my $vm_config_perm_list = [
1150 'VM.Config.Disk',
1151 'VM.Config.CDROM',
1152 'VM.Config.CPU',
1153 'VM.Config.Memory',
1154 'VM.Config.Network',
1155 'VM.Config.HWType',
1156 'VM.Config.Options',
1157 ];
1158
1159__PACKAGE__->register_method({
1160 name => 'update_vm_async',
1161 path => '{vmid}/config',
1162 method => 'POST',
1163 protected => 1,
1164 proxyto => 'node',
1165 description => "Set virtual machine options (asynchrounous API).",
1166 permissions => {
1167 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1168 },
1169 parameters => {
1170 additionalProperties => 0,
1171 properties => PVE::QemuServer::json_config_properties(
1172 {
1173 node => get_standard_option('pve-node'),
1174 vmid => get_standard_option('pve-vmid'),
1175 skiplock => get_standard_option('skiplock'),
1176 delete => {
1177 type => 'string', format => 'pve-configid-list',
1178 description => "A list of settings you want to delete.",
1179 optional => 1,
1180 },
4c8365fa
DM
1181 revert => {
1182 type => 'string', format => 'pve-configid-list',
1183 description => "Revert a pending change.",
1184 optional => 1,
1185 },
5555edea
DM
1186 force => {
1187 type => 'boolean',
1188 description => $opt_force_description,
1189 optional => 1,
1190 requires => 'delete',
1191 },
1192 digest => {
1193 type => 'string',
1194 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1195 maxLength => 40,
1196 optional => 1,
1197 },
1198 background_delay => {
1199 type => 'integer',
1200 description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1201 minimum => 1,
1202 maximum => 30,
1203 optional => 1,
1204 },
1205 }),
1206 },
1207 returns => {
1208 type => 'string',
1209 optional => 1,
1210 },
1211 code => $update_vm_api,
1212});
1213
1214__PACKAGE__->register_method({
1215 name => 'update_vm',
1216 path => '{vmid}/config',
1217 method => 'PUT',
1218 protected => 1,
1219 proxyto => 'node',
1220 description => "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1221 permissions => {
1222 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
1223 },
1224 parameters => {
1225 additionalProperties => 0,
1226 properties => PVE::QemuServer::json_config_properties(
1227 {
1228 node => get_standard_option('pve-node'),
335af808 1229 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
5555edea
DM
1230 skiplock => get_standard_option('skiplock'),
1231 delete => {
1232 type => 'string', format => 'pve-configid-list',
1233 description => "A list of settings you want to delete.",
1234 optional => 1,
1235 },
4c8365fa
DM
1236 revert => {
1237 type => 'string', format => 'pve-configid-list',
1238 description => "Revert a pending change.",
1239 optional => 1,
1240 },
5555edea
DM
1241 force => {
1242 type => 'boolean',
1243 description => $opt_force_description,
1244 optional => 1,
1245 requires => 'delete',
1246 },
1247 digest => {
1248 type => 'string',
1249 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1250 maxLength => 40,
1251 optional => 1,
1252 },
1253 }),
1254 },
1255 returns => { type => 'null' },
1256 code => sub {
1257 my ($param) = @_;
1258 &$update_vm_api($param, 1);
1e3baf05 1259 return undef;
5555edea
DM
1260 }
1261});
1e3baf05
DM
1262
1263
1264__PACKAGE__->register_method({
afdb31d5
DM
1265 name => 'destroy_vm',
1266 path => '{vmid}',
1e3baf05
DM
1267 method => 'DELETE',
1268 protected => 1,
1269 proxyto => 'node',
1270 description => "Destroy the vm (also delete all used/owned volumes).",
a0d1b1a2
DM
1271 permissions => {
1272 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1273 },
1e3baf05
DM
1274 parameters => {
1275 additionalProperties => 0,
1276 properties => {
1277 node => get_standard_option('pve-node'),
335af808 1278 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60 1279 skiplock => get_standard_option('skiplock'),
1e3baf05
DM
1280 },
1281 },
afdb31d5 1282 returns => {
5fdbe4f0
DM
1283 type => 'string',
1284 },
1e3baf05
DM
1285 code => sub {
1286 my ($param) = @_;
1287
1288 my $rpcenv = PVE::RPCEnvironment::get();
1289
a0d1b1a2 1290 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1291
1292 my $vmid = $param->{vmid};
1293
1294 my $skiplock = $param->{skiplock};
afdb31d5 1295 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1296 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1297
5fdbe4f0 1298 # test if VM exists
ffda963f 1299 my $conf = PVE::QemuConfig->load_config($vmid);
5fdbe4f0 1300
afdb31d5 1301 my $storecfg = PVE::Storage::config();
1e3baf05 1302
ffda963f 1303 PVE::QemuConfig->check_protection($conf, "can't remove VM $vmid");
cb0e4540 1304
952e3ac3
DM
1305 die "unable to remove VM $vmid - used in HA resources\n"
1306 if PVE::HA::Config::vm_is_ha_managed($vmid);
e9f2f8e5 1307
628bb7f2
DM
1308 # do not allow destroy if there are replication jobs
1309 my $repl_conf = PVE::ReplicationConfig->new();
1310 $repl_conf->check_for_existing_jobs($vmid);
1311
db593da2
DM
1312 # early tests (repeat after locking)
1313 die "VM $vmid is running - destroy failed\n"
1314 if PVE::QemuServer::check_running($vmid);
1315
5fdbe4f0 1316 my $realcmd = sub {
ff1a2432
DM
1317 my $upid = shift;
1318
1319 syslog('info', "destroy VM $vmid: $upid\n");
1320
5fdbe4f0 1321 PVE::QemuServer::vm_destroy($storecfg, $vmid, $skiplock);
502d18a2 1322
37f43805 1323 PVE::AccessControl::remove_vm_access($vmid);
e9abcde6
AG
1324
1325 PVE::Firewall::remove_vmfw_conf($vmid);
5fdbe4f0 1326 };
1e3baf05 1327
a0d1b1a2 1328 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1e3baf05
DM
1329 }});
1330
1331__PACKAGE__->register_method({
afdb31d5
DM
1332 name => 'unlink',
1333 path => '{vmid}/unlink',
1e3baf05
DM
1334 method => 'PUT',
1335 protected => 1,
1336 proxyto => 'node',
1337 description => "Unlink/delete disk images.",
a0d1b1a2
DM
1338 permissions => {
1339 check => [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1340 },
1e3baf05
DM
1341 parameters => {
1342 additionalProperties => 0,
1343 properties => {
1344 node => get_standard_option('pve-node'),
335af808 1345 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
1e3baf05
DM
1346 idlist => {
1347 type => 'string', format => 'pve-configid-list',
1348 description => "A list of disk IDs you want to delete.",
1349 },
1350 force => {
1351 type => 'boolean',
1352 description => $opt_force_description,
1353 optional => 1,
1354 },
1355 },
1356 },
1357 returns => { type => 'null'},
1358 code => sub {
1359 my ($param) = @_;
1360
1361 $param->{delete} = extract_param($param, 'idlist');
1362
1363 __PACKAGE__->update_vm($param);
1364
1365 return undef;
1366 }});
1367
1368my $sslcert;
1369
1370__PACKAGE__->register_method({
afdb31d5
DM
1371 name => 'vncproxy',
1372 path => '{vmid}/vncproxy',
1e3baf05
DM
1373 method => 'POST',
1374 protected => 1,
1375 permissions => {
378b359e 1376 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1e3baf05
DM
1377 },
1378 description => "Creates a TCP VNC proxy connections.",
1379 parameters => {
1380 additionalProperties => 0,
1381 properties => {
1382 node => get_standard_option('pve-node'),
1383 vmid => get_standard_option('pve-vmid'),
b4d5c000
SP
1384 websocket => {
1385 optional => 1,
1386 type => 'boolean',
1387 description => "starts websockify instead of vncproxy",
1388 },
1e3baf05
DM
1389 },
1390 },
afdb31d5 1391 returns => {
1e3baf05
DM
1392 additionalProperties => 0,
1393 properties => {
1394 user => { type => 'string' },
1395 ticket => { type => 'string' },
1396 cert => { type => 'string' },
1397 port => { type => 'integer' },
1398 upid => { type => 'string' },
1399 },
1400 },
1401 code => sub {
1402 my ($param) = @_;
1403
1404 my $rpcenv = PVE::RPCEnvironment::get();
1405
a0d1b1a2 1406 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1407
1408 my $vmid = $param->{vmid};
1409 my $node = $param->{node};
983d4582 1410 my $websocket = $param->{websocket};
1e3baf05 1411
ffda963f 1412 my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
ef5e2be2 1413
b6f39da2
DM
1414 my $authpath = "/vms/$vmid";
1415
a0d1b1a2 1416 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
b6f39da2 1417
1e3baf05
DM
1418 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1419 if !$sslcert;
1420
af0eba7e 1421 my ($remip, $family);
ef5e2be2 1422 my $remcmd = [];
afdb31d5 1423
4f1be36c 1424 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
af0eba7e 1425 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
b4d5c000 1426 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
ef5e2be2 1427 $remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
af0eba7e
WB
1428 } else {
1429 $family = PVE::Tools::get_host_address_family($node);
1e3baf05
DM
1430 }
1431
af0eba7e
WB
1432 my $port = PVE::Tools::next_vnc_port($family);
1433
afdb31d5 1434 my $timeout = 10;
1e3baf05
DM
1435
1436 my $realcmd = sub {
1437 my $upid = shift;
1438
1439 syslog('info', "starting vnc proxy $upid\n");
1440
ef5e2be2 1441 my $cmd;
1e3baf05 1442
2dc23d72 1443 if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
ef5e2be2 1444
983d4582 1445 die "Websocket mode is not supported in vga serial mode!" if $websocket;
b4d5c000 1446
ef5e2be2
DM
1447 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga} ];
1448 #my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
1449 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
fa8ea931 1450 '-timeout', $timeout, '-authpath', $authpath,
ef5e2be2 1451 '-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
655d7462 1452 PVE::Tools::run_command($cmd);
ef5e2be2 1453 } else {
1e3baf05 1454
3e7567e0
DM
1455 $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy"
1456
655d7462
WB
1457 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1458
1459 my $sock = IO::Socket::IP->new(
dd32a466 1460 ReuseAddr => 1,
655d7462
WB
1461 Listen => 1,
1462 LocalPort => $port,
1463 Proto => 'tcp',
1464 GetAddrInfoFlags => 0,
1465 ) or die "failed to create socket: $!\n";
1466 # Inside the worker we shouldn't have any previous alarms
1467 # running anyway...:
1468 alarm(0);
1469 local $SIG{ALRM} = sub { die "connection timed out\n" };
1470 alarm $timeout;
1471 accept(my $cli, $sock) or die "connection failed: $!\n";
058ff55b 1472 alarm(0);
655d7462
WB
1473 close($sock);
1474 if (PVE::Tools::run_command($cmd,
1475 output => '>&'.fileno($cli),
1476 input => '<&'.fileno($cli),
1477 noerr => 1) != 0)
1478 {
1479 die "Failed to run vncproxy.\n";
1480 }
ef5e2be2 1481 }
1e3baf05 1482
1e3baf05
DM
1483 return;
1484 };
1485
2c7fc947 1486 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1e3baf05 1487
3da85107
DM
1488 PVE::Tools::wait_for_vnc_port($port);
1489
1e3baf05 1490 return {
a0d1b1a2 1491 user => $authuser,
1e3baf05 1492 ticket => $ticket,
afdb31d5
DM
1493 port => $port,
1494 upid => $upid,
1495 cert => $sslcert,
1e3baf05
DM
1496 };
1497 }});
1498
3e7567e0
DM
1499__PACKAGE__->register_method({
1500 name => 'vncwebsocket',
1501 path => '{vmid}/vncwebsocket',
1502 method => 'GET',
3e7567e0 1503 permissions => {
c422ce93 1504 description => "You also need to pass a valid ticket (vncticket).",
3e7567e0
DM
1505 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1506 },
4d00f52f 1507 description => "Opens a weksocket for VNC traffic.",
3e7567e0
DM
1508 parameters => {
1509 additionalProperties => 0,
1510 properties => {
1511 node => get_standard_option('pve-node'),
1512 vmid => get_standard_option('pve-vmid'),
c422ce93
DM
1513 vncticket => {
1514 description => "Ticket from previous call to vncproxy.",
1515 type => 'string',
1516 maxLength => 512,
1517 },
3e7567e0
DM
1518 port => {
1519 description => "Port number returned by previous vncproxy call.",
1520 type => 'integer',
1521 minimum => 5900,
1522 maximum => 5999,
1523 },
1524 },
1525 },
1526 returns => {
1527 type => "object",
1528 properties => {
1529 port => { type => 'string' },
1530 },
1531 },
1532 code => sub {
1533 my ($param) = @_;
1534
1535 my $rpcenv = PVE::RPCEnvironment::get();
1536
1537 my $authuser = $rpcenv->get_user();
1538
1539 my $vmid = $param->{vmid};
1540 my $node = $param->{node};
1541
c422ce93
DM
1542 my $authpath = "/vms/$vmid";
1543
1544 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
1545
ffda963f 1546 my $conf = PVE::QemuConfig->load_config($vmid, $node); # VM exists ?
3e7567e0
DM
1547
1548 # Note: VNC ports are acessible from outside, so we do not gain any
1549 # security if we verify that $param->{port} belongs to VM $vmid. This
1550 # check is done by verifying the VNC ticket (inside VNC protocol).
1551
1552 my $port = $param->{port};
f34ebd52 1553
3e7567e0
DM
1554 return { port => $port };
1555 }});
1556
288eeea8
DM
1557__PACKAGE__->register_method({
1558 name => 'spiceproxy',
1559 path => '{vmid}/spiceproxy',
78252ce7 1560 method => 'POST',
288eeea8 1561 protected => 1,
78252ce7 1562 proxyto => 'node',
288eeea8
DM
1563 permissions => {
1564 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1565 },
1566 description => "Returns a SPICE configuration to connect to the VM.",
1567 parameters => {
1568 additionalProperties => 0,
1569 properties => {
1570 node => get_standard_option('pve-node'),
1571 vmid => get_standard_option('pve-vmid'),
dd25eecf 1572 proxy => get_standard_option('spice-proxy', { optional => 1 }),
288eeea8
DM
1573 },
1574 },
dd25eecf 1575 returns => get_standard_option('remote-viewer-config'),
288eeea8
DM
1576 code => sub {
1577 my ($param) = @_;
1578
1579 my $rpcenv = PVE::RPCEnvironment::get();
1580
1581 my $authuser = $rpcenv->get_user();
1582
1583 my $vmid = $param->{vmid};
1584 my $node = $param->{node};
fb6c7260 1585 my $proxy = $param->{proxy};
288eeea8 1586
ffda963f 1587 my $conf = PVE::QemuConfig->load_config($vmid, $node);
7f9e28e4
TL
1588 my $title = "VM $vmid";
1589 $title .= " - ". $conf->{name} if $conf->{name};
288eeea8 1590
943340a6 1591 my $port = PVE::QemuServer::spice_port($vmid);
dd25eecf 1592
f34ebd52 1593 my ($ticket, undef, $remote_viewer_config) =
dd25eecf 1594 PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
f34ebd52 1595
288eeea8
DM
1596 PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
1597 PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
f34ebd52 1598
dd25eecf 1599 return $remote_viewer_config;
288eeea8
DM
1600 }});
1601
5fdbe4f0
DM
1602__PACKAGE__->register_method({
1603 name => 'vmcmdidx',
afdb31d5 1604 path => '{vmid}/status',
5fdbe4f0
DM
1605 method => 'GET',
1606 proxyto => 'node',
1607 description => "Directory index",
a0d1b1a2
DM
1608 permissions => {
1609 user => 'all',
1610 },
5fdbe4f0
DM
1611 parameters => {
1612 additionalProperties => 0,
1613 properties => {
1614 node => get_standard_option('pve-node'),
1615 vmid => get_standard_option('pve-vmid'),
1616 },
1617 },
1618 returns => {
1619 type => 'array',
1620 items => {
1621 type => "object",
1622 properties => {
1623 subdir => { type => 'string' },
1624 },
1625 },
1626 links => [ { rel => 'child', href => "{subdir}" } ],
1627 },
1628 code => sub {
1629 my ($param) = @_;
1630
1631 # test if VM exists
ffda963f 1632 my $conf = PVE::QemuConfig->load_config($param->{vmid});
5fdbe4f0
DM
1633
1634 my $res = [
1635 { subdir => 'current' },
1636 { subdir => 'start' },
1637 { subdir => 'stop' },
1638 ];
afdb31d5 1639
5fdbe4f0
DM
1640 return $res;
1641 }});
1642
1e3baf05 1643__PACKAGE__->register_method({
afdb31d5 1644 name => 'vm_status',
5fdbe4f0 1645 path => '{vmid}/status/current',
1e3baf05
DM
1646 method => 'GET',
1647 proxyto => 'node',
1648 protected => 1, # qemu pid files are only readable by root
1649 description => "Get virtual machine status.",
a0d1b1a2
DM
1650 permissions => {
1651 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1652 },
1e3baf05
DM
1653 parameters => {
1654 additionalProperties => 0,
1655 properties => {
1656 node => get_standard_option('pve-node'),
1657 vmid => get_standard_option('pve-vmid'),
1658 },
1659 },
1660 returns => { type => 'object' },
1661 code => sub {
1662 my ($param) = @_;
1663
1664 # test if VM exists
ffda963f 1665 my $conf = PVE::QemuConfig->load_config($param->{vmid});
1e3baf05 1666
03a33f30 1667 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1668 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1669
4d2a734e 1670 $status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
8610701a 1671
86b8228b 1672 $status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
46246f04 1673
8610701a 1674 return $status;
1e3baf05
DM
1675 }});
1676
1677__PACKAGE__->register_method({
afdb31d5 1678 name => 'vm_start',
5fdbe4f0
DM
1679 path => '{vmid}/status/start',
1680 method => 'POST',
1e3baf05
DM
1681 protected => 1,
1682 proxyto => 'node',
5fdbe4f0 1683 description => "Start virtual machine.",
a0d1b1a2
DM
1684 permissions => {
1685 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1686 },
1e3baf05
DM
1687 parameters => {
1688 additionalProperties => 0,
1689 properties => {
1690 node => get_standard_option('pve-node'),
ab5904f7
TL
1691 vmid => get_standard_option('pve-vmid',
1692 { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60
DM
1693 skiplock => get_standard_option('skiplock'),
1694 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1695 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
2de2d6f7
TL
1696 migration_type => {
1697 type => 'string',
1698 enum => ['secure', 'insecure'],
1699 description => "Migration traffic is encrypted using an SSH " .
1700 "tunnel by default. On secure, completely private networks " .
1701 "this can be disabled to increase performance.",
1702 optional => 1,
1703 },
1704 migration_network => {
29ddbe70 1705 type => 'string', format => 'CIDR',
2de2d6f7
TL
1706 description => "CIDR of the (sub) network that is used for migration.",
1707 optional => 1,
1708 },
952958bc 1709 machine => get_standard_option('pve-qm-machine'),
2189246c 1710 targetstorage => {
bd2d5fe6 1711 description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2189246c
AD
1712 type => 'string',
1713 optional => 1
1714 }
1e3baf05
DM
1715 },
1716 },
afdb31d5 1717 returns => {
5fdbe4f0
DM
1718 type => 'string',
1719 },
1e3baf05
DM
1720 code => sub {
1721 my ($param) = @_;
1722
1723 my $rpcenv = PVE::RPCEnvironment::get();
1724
a0d1b1a2 1725 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1726
1727 my $node = extract_param($param, 'node');
1728
1e3baf05
DM
1729 my $vmid = extract_param($param, 'vmid');
1730
952958bc
DM
1731 my $machine = extract_param($param, 'machine');
1732
3ea94c60 1733 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1734 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1735 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1736
1e3baf05 1737 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1738 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1739 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1740
7e8dcf2c
AD
1741 my $migratedfrom = extract_param($param, 'migratedfrom');
1742 raise_param_exc({ migratedfrom => "Only root may use this option." })
1743 if $migratedfrom && $authuser ne 'root@pam';
1744
2de2d6f7
TL
1745 my $migration_type = extract_param($param, 'migration_type');
1746 raise_param_exc({ migration_type => "Only root may use this option." })
1747 if $migration_type && $authuser ne 'root@pam';
1748
1749 my $migration_network = extract_param($param, 'migration_network');
1750 raise_param_exc({ migration_network => "Only root may use this option." })
1751 if $migration_network && $authuser ne 'root@pam';
1752
2189246c
AD
1753 my $targetstorage = extract_param($param, 'targetstorage');
1754 raise_param_exc({ targetstorage => "Only root may use this option." })
1755 if $targetstorage && $authuser ne 'root@pam';
1756
1757 raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
1758 if $targetstorage && !$migratedfrom;
1759
7c14dcae
DM
1760 # read spice ticket from STDIN
1761 my $spice_ticket;
1762 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
a64d6146 1763 if (defined(my $line = <>)) {
760fb3c8
DM
1764 chomp $line;
1765 $spice_ticket = $line;
1766 }
7c14dcae
DM
1767 }
1768
98cbd0f4
WB
1769 PVE::Cluster::check_cfs_quorum();
1770
afdb31d5 1771 my $storecfg = PVE::Storage::config();
5fdbe4f0 1772
2003f0f8 1773 if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri &&
cce37749 1774 $rpcenv->{type} ne 'ha') {
5fdbe4f0 1775
88fc87b4
DM
1776 my $hacmd = sub {
1777 my $upid = shift;
5fdbe4f0 1778
c44291cd 1779 my $service = "vm:$vmid";
5fdbe4f0 1780
2a7e2b82 1781 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
88fc87b4 1782
02765844 1783 print "Requesting HA start for VM $vmid\n";
88fc87b4
DM
1784
1785 PVE::Tools::run_command($cmd);
1786
1787 return;
1788 };
1789
1790 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1791
1792 } else {
1793
1794 my $realcmd = sub {
1795 my $upid = shift;
1796
1797 syslog('info', "start VM $vmid: $upid\n");
1798
fa8ea931 1799 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2189246c 1800 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
88fc87b4
DM
1801
1802 return;
1803 };
5fdbe4f0 1804
88fc87b4
DM
1805 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1806 }
5fdbe4f0
DM
1807 }});
1808
1809__PACKAGE__->register_method({
afdb31d5 1810 name => 'vm_stop',
5fdbe4f0
DM
1811 path => '{vmid}/status/stop',
1812 method => 'POST',
1813 protected => 1,
1814 proxyto => 'node',
346130b2 1815 description => "Stop virtual machine. The qemu process will exit immediately. This" .
d6c747ff 1816 "is akin to pulling the power plug of a running computer and may damage the VM data",
a0d1b1a2
DM
1817 permissions => {
1818 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1819 },
5fdbe4f0
DM
1820 parameters => {
1821 additionalProperties => 0,
1822 properties => {
1823 node => get_standard_option('pve-node'),
ab5904f7
TL
1824 vmid => get_standard_option('pve-vmid',
1825 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 1826 skiplock => get_standard_option('skiplock'),
debe8882 1827 migratedfrom => get_standard_option('pve-node', { optional => 1 }),
c6bb9502
DM
1828 timeout => {
1829 description => "Wait maximal timeout seconds.",
1830 type => 'integer',
1831 minimum => 0,
1832 optional => 1,
254575e9
DM
1833 },
1834 keepActive => {
94a17e1d 1835 description => "Do not deactivate storage volumes.",
254575e9
DM
1836 type => 'boolean',
1837 optional => 1,
1838 default => 0,
c6bb9502 1839 }
5fdbe4f0
DM
1840 },
1841 },
afdb31d5 1842 returns => {
5fdbe4f0
DM
1843 type => 'string',
1844 },
1845 code => sub {
1846 my ($param) = @_;
1847
1848 my $rpcenv = PVE::RPCEnvironment::get();
1849
a0d1b1a2 1850 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1851
1852 my $node = extract_param($param, 'node');
1853
1854 my $vmid = extract_param($param, 'vmid');
1855
1856 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1857 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1858 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1859
254575e9 1860 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1861 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1862 if $keepActive && $authuser ne 'root@pam';
254575e9 1863
af30308f
DM
1864 my $migratedfrom = extract_param($param, 'migratedfrom');
1865 raise_param_exc({ migratedfrom => "Only root may use this option." })
1866 if $migratedfrom && $authuser ne 'root@pam';
1867
1868
ff1a2432
DM
1869 my $storecfg = PVE::Storage::config();
1870
2003f0f8 1871 if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
5fdbe4f0 1872
88fc87b4
DM
1873 my $hacmd = sub {
1874 my $upid = shift;
5fdbe4f0 1875
c44291cd 1876 my $service = "vm:$vmid";
c6bb9502 1877
e0feef86 1878 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
88fc87b4 1879
02765844 1880 print "Requesting HA stop for VM $vmid\n";
88fc87b4
DM
1881
1882 PVE::Tools::run_command($cmd);
5fdbe4f0 1883
88fc87b4
DM
1884 return;
1885 };
1886
1887 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1888
1889 } else {
1890 my $realcmd = sub {
1891 my $upid = shift;
1892
1893 syslog('info', "stop VM $vmid: $upid\n");
1894
1895 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 1896 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
1897
1898 return;
1899 };
1900
1901 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1902 }
5fdbe4f0
DM
1903 }});
1904
1905__PACKAGE__->register_method({
afdb31d5 1906 name => 'vm_reset',
5fdbe4f0
DM
1907 path => '{vmid}/status/reset',
1908 method => 'POST',
1909 protected => 1,
1910 proxyto => 'node',
1911 description => "Reset virtual machine.",
a0d1b1a2
DM
1912 permissions => {
1913 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1914 },
5fdbe4f0
DM
1915 parameters => {
1916 additionalProperties => 0,
1917 properties => {
1918 node => get_standard_option('pve-node'),
ab5904f7
TL
1919 vmid => get_standard_option('pve-vmid',
1920 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
1921 skiplock => get_standard_option('skiplock'),
1922 },
1923 },
afdb31d5 1924 returns => {
5fdbe4f0
DM
1925 type => 'string',
1926 },
1927 code => sub {
1928 my ($param) = @_;
1929
1930 my $rpcenv = PVE::RPCEnvironment::get();
1931
a0d1b1a2 1932 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1933
1934 my $node = extract_param($param, 'node');
1935
1936 my $vmid = extract_param($param, 'vmid');
1937
1938 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1939 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1940 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1941
ff1a2432
DM
1942 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1943
5fdbe4f0
DM
1944 my $realcmd = sub {
1945 my $upid = shift;
1946
1e3baf05 1947 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1948
1949 return;
1950 };
1951
a0d1b1a2 1952 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1953 }});
1954
1955__PACKAGE__->register_method({
afdb31d5 1956 name => 'vm_shutdown',
5fdbe4f0
DM
1957 path => '{vmid}/status/shutdown',
1958 method => 'POST',
1959 protected => 1,
1960 proxyto => 'node',
d6c747ff
EK
1961 description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
1962 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
a0d1b1a2
DM
1963 permissions => {
1964 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1965 },
5fdbe4f0
DM
1966 parameters => {
1967 additionalProperties => 0,
1968 properties => {
1969 node => get_standard_option('pve-node'),
ab5904f7
TL
1970 vmid => get_standard_option('pve-vmid',
1971 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 1972 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1973 timeout => {
1974 description => "Wait maximal timeout seconds.",
1975 type => 'integer',
1976 minimum => 0,
1977 optional => 1,
9269013a
DM
1978 },
1979 forceStop => {
1980 description => "Make sure the VM stops.",
1981 type => 'boolean',
1982 optional => 1,
1983 default => 0,
254575e9
DM
1984 },
1985 keepActive => {
94a17e1d 1986 description => "Do not deactivate storage volumes.",
254575e9
DM
1987 type => 'boolean',
1988 optional => 1,
1989 default => 0,
c6bb9502 1990 }
5fdbe4f0
DM
1991 },
1992 },
afdb31d5 1993 returns => {
5fdbe4f0
DM
1994 type => 'string',
1995 },
1996 code => sub {
1997 my ($param) = @_;
1998
1999 my $rpcenv = PVE::RPCEnvironment::get();
2000
a0d1b1a2 2001 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2002
2003 my $node = extract_param($param, 'node');
2004
2005 my $vmid = extract_param($param, 'vmid');
2006
2007 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2008 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2009 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2010
254575e9 2011 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2012 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2013 if $keepActive && $authuser ne 'root@pam';
254575e9 2014
02d07cf5
DM
2015 my $storecfg = PVE::Storage::config();
2016
89897367
DC
2017 my $shutdown = 1;
2018
2019 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2020 # otherwise, we will infer a shutdown command, but run into the timeout,
2021 # then when the vm is resumed, it will instantly shutdown
2022 #
2023 # checking the qmp status here to get feedback to the gui/cli/api
2024 # and the status query should not take too long
2025 my $qmpstatus;
2026 eval {
2027 $qmpstatus = PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
2028 };
2029 my $err = $@ if $@;
2030
2031 if (!$err && $qmpstatus->{status} eq "paused") {
2032 if ($param->{forceStop}) {
2033 warn "VM is paused - stop instead of shutdown\n";
2034 $shutdown = 0;
2035 } else {
2036 die "VM is paused - cannot shutdown\n";
2037 }
2038 }
2039
ae849692
DM
2040 if (PVE::HA::Config::vm_is_ha_managed($vmid) &&
2041 ($rpcenv->{type} ne 'ha')) {
5fdbe4f0 2042
ae849692
DM
2043 my $hacmd = sub {
2044 my $upid = shift;
5fdbe4f0 2045
ae849692 2046 my $service = "vm:$vmid";
c6bb9502 2047
ae849692
DM
2048 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2049
02765844 2050 print "Requesting HA stop for VM $vmid\n";
ae849692
DM
2051
2052 PVE::Tools::run_command($cmd);
2053
2054 return;
2055 };
2056
2057 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2058
2059 } else {
2060
2061 my $realcmd = sub {
2062 my $upid = shift;
2063
2064 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2065
ae849692
DM
2066 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2067 $shutdown, $param->{forceStop}, $keepActive);
2068
2069 return;
2070 };
2071
2072 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2073 }
5fdbe4f0
DM
2074 }});
2075
2076__PACKAGE__->register_method({
afdb31d5 2077 name => 'vm_suspend',
5fdbe4f0
DM
2078 path => '{vmid}/status/suspend',
2079 method => 'POST',
2080 protected => 1,
2081 proxyto => 'node',
2082 description => "Suspend virtual machine.",
a0d1b1a2
DM
2083 permissions => {
2084 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2085 },
5fdbe4f0
DM
2086 parameters => {
2087 additionalProperties => 0,
2088 properties => {
2089 node => get_standard_option('pve-node'),
ab5904f7
TL
2090 vmid => get_standard_option('pve-vmid',
2091 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2092 skiplock => get_standard_option('skiplock'),
2093 },
2094 },
afdb31d5 2095 returns => {
5fdbe4f0
DM
2096 type => 'string',
2097 },
2098 code => sub {
2099 my ($param) = @_;
2100
2101 my $rpcenv = PVE::RPCEnvironment::get();
2102
a0d1b1a2 2103 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2104
2105 my $node = extract_param($param, 'node');
2106
2107 my $vmid = extract_param($param, 'vmid');
2108
2109 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2110 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2111 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2112
ff1a2432
DM
2113 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2114
5fdbe4f0
DM
2115 my $realcmd = sub {
2116 my $upid = shift;
2117
2118 syslog('info', "suspend VM $vmid: $upid\n");
2119
1e3baf05 2120 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
2121
2122 return;
2123 };
2124
a0d1b1a2 2125 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2126 }});
2127
2128__PACKAGE__->register_method({
afdb31d5 2129 name => 'vm_resume',
5fdbe4f0
DM
2130 path => '{vmid}/status/resume',
2131 method => 'POST',
2132 protected => 1,
2133 proxyto => 'node',
2134 description => "Resume virtual machine.",
a0d1b1a2
DM
2135 permissions => {
2136 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2137 },
5fdbe4f0
DM
2138 parameters => {
2139 additionalProperties => 0,
2140 properties => {
2141 node => get_standard_option('pve-node'),
ab5904f7
TL
2142 vmid => get_standard_option('pve-vmid',
2143 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2144 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2145 nocheck => { type => 'boolean', optional => 1 },
2146
5fdbe4f0
DM
2147 },
2148 },
afdb31d5 2149 returns => {
5fdbe4f0
DM
2150 type => 'string',
2151 },
2152 code => sub {
2153 my ($param) = @_;
2154
2155 my $rpcenv = PVE::RPCEnvironment::get();
2156
a0d1b1a2 2157 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2158
2159 my $node = extract_param($param, 'node');
2160
2161 my $vmid = extract_param($param, 'vmid');
2162
2163 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2164 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2165 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2166
289e0b85
AD
2167 my $nocheck = extract_param($param, 'nocheck');
2168
2169 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2170
5fdbe4f0
DM
2171 my $realcmd = sub {
2172 my $upid = shift;
2173
2174 syslog('info', "resume VM $vmid: $upid\n");
2175
289e0b85 2176 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
1e3baf05 2177
5fdbe4f0
DM
2178 return;
2179 };
2180
a0d1b1a2 2181 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2182 }});
2183
2184__PACKAGE__->register_method({
afdb31d5 2185 name => 'vm_sendkey',
5fdbe4f0
DM
2186 path => '{vmid}/sendkey',
2187 method => 'PUT',
2188 protected => 1,
2189 proxyto => 'node',
2190 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2191 permissions => {
2192 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2193 },
5fdbe4f0
DM
2194 parameters => {
2195 additionalProperties => 0,
2196 properties => {
2197 node => get_standard_option('pve-node'),
ab5904f7
TL
2198 vmid => get_standard_option('pve-vmid',
2199 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2200 skiplock => get_standard_option('skiplock'),
2201 key => {
2202 description => "The key (qemu monitor encoding).",
2203 type => 'string'
2204 }
2205 },
2206 },
2207 returns => { type => 'null'},
2208 code => sub {
2209 my ($param) = @_;
2210
2211 my $rpcenv = PVE::RPCEnvironment::get();
2212
a0d1b1a2 2213 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2214
2215 my $node = extract_param($param, 'node');
2216
2217 my $vmid = extract_param($param, 'vmid');
2218
2219 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2220 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2221 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2222
2223 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2224
2225 return;
1e3baf05
DM
2226 }});
2227
1ac0d2ee
AD
2228__PACKAGE__->register_method({
2229 name => 'vm_feature',
2230 path => '{vmid}/feature',
2231 method => 'GET',
2232 proxyto => 'node',
75466c4f 2233 protected => 1,
1ac0d2ee
AD
2234 description => "Check if feature for virtual machine is available.",
2235 permissions => {
2236 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2237 },
2238 parameters => {
2239 additionalProperties => 0,
2240 properties => {
2241 node => get_standard_option('pve-node'),
2242 vmid => get_standard_option('pve-vmid'),
2243 feature => {
2244 description => "Feature to check.",
2245 type => 'string',
7758ce86 2246 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2247 },
2248 snapname => get_standard_option('pve-snapshot-name', {
2249 optional => 1,
2250 }),
2251 },
1ac0d2ee
AD
2252 },
2253 returns => {
719893a9
DM
2254 type => "object",
2255 properties => {
2256 hasFeature => { type => 'boolean' },
7043d946 2257 nodes => {
719893a9
DM
2258 type => 'array',
2259 items => { type => 'string' },
2260 }
2261 },
1ac0d2ee
AD
2262 },
2263 code => sub {
2264 my ($param) = @_;
2265
2266 my $node = extract_param($param, 'node');
2267
2268 my $vmid = extract_param($param, 'vmid');
2269
2270 my $snapname = extract_param($param, 'snapname');
2271
2272 my $feature = extract_param($param, 'feature');
2273
2274 my $running = PVE::QemuServer::check_running($vmid);
2275
ffda963f 2276 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2277
2278 if($snapname){
2279 my $snap = $conf->{snapshots}->{$snapname};
2280 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2281 $conf = $snap;
2282 }
2283 my $storecfg = PVE::Storage::config();
2284
719893a9 2285 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2286 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2287
719893a9
DM
2288 return {
2289 hasFeature => $hasFeature,
2290 nodes => [ keys %$nodelist ],
7043d946 2291 };
1ac0d2ee
AD
2292 }});
2293
6116f729 2294__PACKAGE__->register_method({
9418baad
DM
2295 name => 'clone_vm',
2296 path => '{vmid}/clone',
6116f729
DM
2297 method => 'POST',
2298 protected => 1,
2299 proxyto => 'node',
37329185 2300 description => "Create a copy of virtual machine/template.",
6116f729 2301 permissions => {
9418baad 2302 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2303 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2304 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2305 check =>
2306 [ 'and',
9418baad 2307 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2308 [ 'or',
6116f729
DM
2309 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2310 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2311 ],
2312 ]
2313 },
2314 parameters => {
2315 additionalProperties => 0,
2316 properties => {
6116f729 2317 node => get_standard_option('pve-node'),
335af808 2318 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
9418baad 2319 newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }),
a60ab1a6
DM
2320 name => {
2321 optional => 1,
2322 type => 'string', format => 'dns-name',
2323 description => "Set a name for the new VM.",
2324 },
2325 description => {
2326 optional => 1,
2327 type => 'string',
2328 description => "Description for the new VM.",
2329 },
75466c4f 2330 pool => {
6116f729
DM
2331 optional => 1,
2332 type => 'string', format => 'pve-poolid',
2333 description => "Add the new VM to the specified pool.",
2334 },
9076d880 2335 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2336 optional => 1,
2337 }),
81f043eb 2338 storage => get_standard_option('pve-storage-id', {
9418baad 2339 description => "Target storage for full clone.",
4e4f83fe 2340 requires => 'full',
81f043eb
AD
2341 optional => 1,
2342 }),
55173c6b 2343 'format' => {
42a19c87
AD
2344 description => "Target format for file storage.",
2345 requires => 'full',
2346 type => 'string',
2347 optional => 1,
55173c6b 2348 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2349 },
6116f729
DM
2350 full => {
2351 optional => 1,
55173c6b
DM
2352 type => 'boolean',
2353 description => "Create a full copy of all disk. This is always done when " .
9418baad 2354 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729
DM
2355 default => 0,
2356 },
75466c4f 2357 target => get_standard_option('pve-node', {
55173c6b
DM
2358 description => "Target node. Only allowed if the original VM is on shared storage.",
2359 optional => 1,
2360 }),
2361 },
6116f729
DM
2362 },
2363 returns => {
2364 type => 'string',
2365 },
2366 code => sub {
2367 my ($param) = @_;
2368
2369 my $rpcenv = PVE::RPCEnvironment::get();
2370
55173c6b 2371 my $authuser = $rpcenv->get_user();
6116f729
DM
2372
2373 my $node = extract_param($param, 'node');
2374
2375 my $vmid = extract_param($param, 'vmid');
2376
2377 my $newid = extract_param($param, 'newid');
2378
6116f729
DM
2379 my $pool = extract_param($param, 'pool');
2380
2381 if (defined($pool)) {
2382 $rpcenv->check_pool_exist($pool);
2383 }
2384
55173c6b 2385 my $snapname = extract_param($param, 'snapname');
9076d880 2386
81f043eb
AD
2387 my $storage = extract_param($param, 'storage');
2388
42a19c87
AD
2389 my $format = extract_param($param, 'format');
2390
55173c6b
DM
2391 my $target = extract_param($param, 'target');
2392
2393 my $localnode = PVE::INotify::nodename();
2394
751cc556 2395 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2396
2397 PVE::Cluster::check_node_exists($target) if $target;
2398
6116f729
DM
2399 my $storecfg = PVE::Storage::config();
2400
4a5a2590
DM
2401 if ($storage) {
2402 # check if storage is enabled on local node
2403 PVE::Storage::storage_check_enabled($storecfg, $storage);
2404 if ($target) {
2405 # check if storage is available on target node
2406 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2407 # clone only works if target storage is shared
2408 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2409 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2410 }
2411 }
2412
55173c6b 2413 PVE::Cluster::check_cfs_quorum();
6116f729 2414
4e4f83fe
DM
2415 my $running = PVE::QemuServer::check_running($vmid) || 0;
2416
4e4f83fe
DM
2417 # exclusive lock if VM is running - else shared lock is enough;
2418 my $shared_lock = $running ? 0 : 1;
2419
9418baad 2420 my $clonefn = sub {
6116f729 2421
829967a9
DM
2422 # do all tests after lock
2423 # we also try to do all tests before we fork the worker
2424
ffda963f 2425 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2426
ffda963f 2427 PVE::QemuConfig->check_lock($conf);
6116f729 2428
4e4f83fe 2429 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2430
4e4f83fe 2431 die "unexpected state change\n" if $verify_running != $running;
6116f729 2432
75466c4f
DM
2433 die "snapshot '$snapname' does not exist\n"
2434 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2435
75466c4f 2436 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2437
9418baad 2438 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2439
9418baad 2440 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2441
ffda963f 2442 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2443
2444 die "unable to create VM $newid: config file already exists\n"
2445 if -f $conffile;
2446
9418baad 2447 my $newconf = { lock => 'clone' };
829967a9 2448 my $drives = {};
34456bf0 2449 my $fullclone = {};
829967a9
DM
2450 my $vollist = [];
2451
2452 foreach my $opt (keys %$oldconf) {
2453 my $value = $oldconf->{$opt};
2454
2455 # do not copy snapshot related info
2456 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2457 $opt eq 'vmstate' || $opt eq 'snapstate';
2458
a78ea5df
WL
2459 # no need to copy unused images, because VMID(owner) changes anyways
2460 next if $opt =~ m/^unused\d+$/;
2461
829967a9
DM
2462 # always change MAC! address
2463 if ($opt =~ m/^net(\d+)$/) {
2464 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2465 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2466 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2467 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2468 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2469 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2470 die "unable to parse drive options for '$opt'\n" if !$drive;
829967a9
DM
2471 if (PVE::QemuServer::drive_is_cdrom($drive)) {
2472 $newconf->{$opt} = $value; # simply copy configuration
2473 } else {
64ff6fe4 2474 if ($param->{full}) {
6318daca 2475 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2476 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2477 $fullclone->{$opt} = 1;
64ff6fe4
SP
2478 } else {
2479 # not full means clone instead of copy
6318daca 2480 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2481 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2482 }
829967a9
DM
2483 $drives->{$opt} = $drive;
2484 push @$vollist, $drive->{file};
2485 }
2486 } else {
2487 # copy everything else
2488 $newconf->{$opt} = $value;
2489 }
2490 }
2491
cd11416f
DM
2492 # auto generate a new uuid
2493 my ($uuid, $uuid_str);
2494 UUID::generate($uuid);
2495 UUID::unparse($uuid, $uuid_str);
2496 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
f34ebd52 2497 $smbios1->{uuid} = $uuid_str;
cd11416f
DM
2498 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2499
829967a9
DM
2500 delete $newconf->{template};
2501
2502 if ($param->{name}) {
2503 $newconf->{name} = $param->{name};
2504 } else {
c55fee03
DM
2505 if ($oldconf->{name}) {
2506 $newconf->{name} = "Copy-of-$oldconf->{name}";
2507 } else {
2508 $newconf->{name} = "Copy-of-VM-$vmid";
2509 }
829967a9 2510 }
2dd53043 2511
829967a9
DM
2512 if ($param->{description}) {
2513 $newconf->{description} = $param->{description};
2514 }
2515
6116f729 2516 # create empty/temp config - this fails if VM already exists on other node
9418baad 2517 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2518
2519 my $realcmd = sub {
2520 my $upid = shift;
2521
b83e0181 2522 my $newvollist = [];
c6fdd002 2523 my $jobs = {};
6116f729 2524
b83e0181 2525 eval {
829967a9 2526 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2527
eb15b9f0 2528 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2529
c6fdd002
AD
2530 my $total_jobs = scalar(keys %{$drives});
2531 my $i = 1;
c6fdd002 2532
829967a9
DM
2533 foreach my $opt (keys %$drives) {
2534 my $drive = $drives->{$opt};
3b4cf0f0 2535 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2536
152fe752 2537 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0
WB
2538 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2539 $jobs, $skipcomplete, $oldconf->{agent});
00b095ca 2540
152fe752 2541 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2542
ffda963f 2543 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2544 $i++;
829967a9 2545 }
b83e0181
DM
2546
2547 delete $newconf->{lock};
ffda963f 2548 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2549
2550 if ($target) {
baca276d 2551 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2552 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2553 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2554
ffda963f 2555 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2556 die "Failed to move config to node '$target' - rename failed: $!\n"
2557 if !rename($conffile, $newconffile);
2558 }
d703d4c0 2559
be517049 2560 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2561 };
75466c4f 2562 if (my $err = $@) {
6116f729
DM
2563 unlink $conffile;
2564
c6fdd002
AD
2565 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2566
b83e0181
DM
2567 sleep 1; # some storage like rbd need to wait before release volume - really?
2568
2569 foreach my $volid (@$newvollist) {
2570 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2571 warn $@ if $@;
2572 }
9418baad 2573 die "clone failed: $err";
6116f729
DM
2574 }
2575
2576 return;
2577 };
2578
457010cc
AG
2579 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2580
9418baad 2581 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2582 };
2583
ffda963f 2584 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2585 # Aquire exclusive lock lock for $newid
ffda963f 2586 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2587 });
2588
2589 }});
2590
586bfa78 2591__PACKAGE__->register_method({
43bc02a9
DM
2592 name => 'move_vm_disk',
2593 path => '{vmid}/move_disk',
e2cd75fa 2594 method => 'POST',
586bfa78
AD
2595 protected => 1,
2596 proxyto => 'node',
2597 description => "Move volume to different storage.",
2598 permissions => {
c07a9e3d
DM
2599 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2600 check => [ 'and',
2601 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2602 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2603 ],
586bfa78
AD
2604 },
2605 parameters => {
2606 additionalProperties => 0,
c07a9e3d 2607 properties => {
586bfa78 2608 node => get_standard_option('pve-node'),
335af808 2609 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2610 disk => {
2611 type => 'string',
2612 description => "The disk you want to move.",
74479ee9 2613 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2614 },
335af808
DM
2615 storage => get_standard_option('pve-storage-id', {
2616 description => "Target storage.",
2617 completion => \&PVE::QemuServer::complete_storage,
2618 }),
635c3c44 2619 'format' => {
586bfa78
AD
2620 type => 'string',
2621 description => "Target Format.",
2622 enum => [ 'raw', 'qcow2', 'vmdk' ],
2623 optional => 1,
2624 },
70d45e33
DM
2625 delete => {
2626 type => 'boolean',
2627 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2628 optional => 1,
2629 default => 0,
2630 },
586bfa78
AD
2631 digest => {
2632 type => 'string',
2633 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2634 maxLength => 40,
2635 optional => 1,
2636 },
2637 },
2638 },
e2cd75fa
DM
2639 returns => {
2640 type => 'string',
2641 description => "the task ID.",
2642 },
586bfa78
AD
2643 code => sub {
2644 my ($param) = @_;
2645
2646 my $rpcenv = PVE::RPCEnvironment::get();
2647
2648 my $authuser = $rpcenv->get_user();
2649
2650 my $node = extract_param($param, 'node');
2651
2652 my $vmid = extract_param($param, 'vmid');
2653
2654 my $digest = extract_param($param, 'digest');
2655
2656 my $disk = extract_param($param, 'disk');
2657
2658 my $storeid = extract_param($param, 'storage');
2659
2660 my $format = extract_param($param, 'format');
2661
586bfa78
AD
2662 my $storecfg = PVE::Storage::config();
2663
2664 my $updatefn = sub {
2665
ffda963f 2666 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 2667
dcce9b46
FG
2668 PVE::QemuConfig->check_lock($conf);
2669
586bfa78
AD
2670 die "checksum missmatch (file change by other user?)\n"
2671 if $digest && $digest ne $conf->{digest};
586bfa78
AD
2672
2673 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2674
2675 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2676
70d45e33 2677 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78
AD
2678
2679 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2680
e2cd75fa 2681 my $oldfmt;
70d45e33 2682 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
2683 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2684 $oldfmt = $1;
2685 }
2686
7043d946 2687 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 2688 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 2689
9dbf9b54
FG
2690 # this only checks snapshots because $disk is passed!
2691 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
2692 die "you can't move a disk with snapshots and delete the source\n"
2693 if $snapshotted && $param->{delete};
2694
586bfa78
AD
2695 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2696
2697 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
2698
2699 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
2700
586bfa78
AD
2701 my $realcmd = sub {
2702
2703 my $newvollist = [];
2704
2705 eval {
2706 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
2707
9dbf9b54
FG
2708 warn "moving disk with snapshots, snapshots will not be moved!\n"
2709 if $snapshotted;
2710
e2cd75fa
DM
2711 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2712 $vmid, $storeid, $format, 1, $newvollist);
2713
2714 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2715
8793d495 2716 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2717
fbd7dcce
FG
2718 # convert moved disk to base if part of template
2719 PVE::QemuServer::template_create($vmid, $conf, $disk)
2720 if PVE::QemuConfig->is_template($conf);
2721
ffda963f 2722 PVE::QemuConfig->write_config($vmid, $conf);
73272365 2723
f34ebd52 2724 eval {
73272365 2725 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 2726 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
2727 if !$running;
2728 };
2729 warn $@ if $@;
586bfa78
AD
2730 };
2731 if (my $err = $@) {
2732
2733 foreach my $volid (@$newvollist) {
2734 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2735 warn $@ if $@;
2736 }
2737 die "storage migration failed: $err";
2738 }
70d45e33
DM
2739
2740 if ($param->{delete}) {
a3d0bafb
FG
2741 eval {
2742 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
2743 PVE::Storage::vdisk_free($storecfg, $old_volid);
2744 };
2745 warn $@ if $@;
70d45e33 2746 }
586bfa78
AD
2747 };
2748
2749 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2750 };
e2cd75fa 2751
ffda963f 2752 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
2753 }});
2754
3ea94c60 2755__PACKAGE__->register_method({
afdb31d5 2756 name => 'migrate_vm',
3ea94c60
DM
2757 path => '{vmid}/migrate',
2758 method => 'POST',
2759 protected => 1,
2760 proxyto => 'node',
2761 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2762 permissions => {
2763 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2764 },
3ea94c60
DM
2765 parameters => {
2766 additionalProperties => 0,
2767 properties => {
2768 node => get_standard_option('pve-node'),
335af808
DM
2769 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2770 target => get_standard_option('pve-node', {
2771 description => "Target node.",
2772 completion => \&PVE::Cluster::complete_migration_target,
2773 }),
3ea94c60
DM
2774 online => {
2775 type => 'boolean',
2776 description => "Use online/live migration.",
2777 optional => 1,
2778 },
2779 force => {
2780 type => 'boolean',
2781 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2782 optional => 1,
2783 },
2de2d6f7
TL
2784 migration_type => {
2785 type => 'string',
2786 enum => ['secure', 'insecure'],
c07a9e3d 2787 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
2788 optional => 1,
2789 },
2790 migration_network => {
c07a9e3d 2791 type => 'string', format => 'CIDR',
2de2d6f7
TL
2792 description => "CIDR of the (sub) network that is used for migration.",
2793 optional => 1,
2794 },
56af7146
AD
2795 "with-local-disks" => {
2796 type => 'boolean',
2797 description => "Enable live storage migration for local disk",
b74cad8a 2798 optional => 1,
56af7146
AD
2799 },
2800 targetstorage => get_standard_option('pve-storage-id', {
2801 description => "Default target storage.",
2802 optional => 1,
2803 completion => \&PVE::QemuServer::complete_storage,
2804 }),
3ea94c60
DM
2805 },
2806 },
afdb31d5 2807 returns => {
3ea94c60
DM
2808 type => 'string',
2809 description => "the task ID.",
2810 },
2811 code => sub {
2812 my ($param) = @_;
2813
2814 my $rpcenv = PVE::RPCEnvironment::get();
2815
a0d1b1a2 2816 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2817
2818 my $target = extract_param($param, 'target');
2819
2820 my $localnode = PVE::INotify::nodename();
2821 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2822
2823 PVE::Cluster::check_cfs_quorum();
2824
2825 PVE::Cluster::check_node_exists($target);
2826
2827 my $targetip = PVE::Cluster::remote_node_ip($target);
2828
2829 my $vmid = extract_param($param, 'vmid');
2830
bd2d5fe6 2831 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
2832 if !$param->{online} && $param->{targetstorage};
2833
afdb31d5 2834 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2835 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 2836
2de2d6f7
TL
2837 raise_param_exc({ migration_type => "Only root may use this option." })
2838 if $param->{migration_type} && $authuser ne 'root@pam';
2839
2840 # allow root only until better network permissions are available
2841 raise_param_exc({ migration_network => "Only root may use this option." })
2842 if $param->{migration_network} && $authuser ne 'root@pam';
2843
3ea94c60 2844 # test if VM exists
ffda963f 2845 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
2846
2847 # try to detect errors early
a5ed42d3 2848
ffda963f 2849 PVE::QemuConfig->check_lock($conf);
a5ed42d3 2850
3ea94c60 2851 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2852 die "cant migrate running VM without --online\n"
3ea94c60
DM
2853 if !$param->{online};
2854 }
2855
47152e2e 2856 my $storecfg = PVE::Storage::config();
d80ad67f
AD
2857
2858 if( $param->{targetstorage}) {
2859 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
2860 } else {
2861 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
2862 }
47152e2e 2863
2003f0f8 2864 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2865
88fc87b4
DM
2866 my $hacmd = sub {
2867 my $upid = shift;
3ea94c60 2868
c44291cd 2869 my $service = "vm:$vmid";
88fc87b4 2870
2003f0f8 2871 my $cmd = ['ha-manager', 'migrate', $service, $target];
88fc87b4 2872
02765844 2873 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4
DM
2874
2875 PVE::Tools::run_command($cmd);
2876
2877 return;
2878 };
2879
2880 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2881
2882 } else {
2883
f53c6ad8 2884 my $realcmd = sub {
f53c6ad8
DM
2885 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2886 };
88fc87b4 2887
f53c6ad8
DM
2888 my $worker = sub {
2889 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
2890 };
2891
f53c6ad8 2892 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 2893 }
3ea94c60 2894
3ea94c60 2895 }});
1e3baf05 2896
91c94f0a 2897__PACKAGE__->register_method({
afdb31d5
DM
2898 name => 'monitor',
2899 path => '{vmid}/monitor',
91c94f0a
DM
2900 method => 'POST',
2901 protected => 1,
2902 proxyto => 'node',
2903 description => "Execute Qemu monitor commands.",
a0d1b1a2 2904 permissions => {
a8f2f427 2905 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 2906 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 2907 },
91c94f0a
DM
2908 parameters => {
2909 additionalProperties => 0,
2910 properties => {
2911 node => get_standard_option('pve-node'),
2912 vmid => get_standard_option('pve-vmid'),
2913 command => {
2914 type => 'string',
2915 description => "The monitor command.",
2916 }
2917 },
2918 },
2919 returns => { type => 'string'},
2920 code => sub {
2921 my ($param) = @_;
2922
a8f2f427
FG
2923 my $rpcenv = PVE::RPCEnvironment::get();
2924 my $authuser = $rpcenv->get_user();
2925
2926 my $is_ro = sub {
2927 my $command = shift;
2928 return $command =~ m/^\s*info(\s+|$)/
2929 || $command =~ m/^\s*help\s*$/;
2930 };
2931
2932 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
2933 if !&$is_ro($param->{command});
2934
91c94f0a
DM
2935 my $vmid = $param->{vmid};
2936
ffda963f 2937 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
2938
2939 my $res = '';
2940 eval {
7b7c6d1b 2941 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2942 };
2943 $res = "ERROR: $@" if $@;
2944
2945 return $res;
2946 }});
2947
a5d5341c 2948my $guest_agent_commands = [
249d8fed
DM
2949 'ping',
2950 'get-time',
2951 'info',
2952 'fsfreeze-status',
2953 'fsfreeze-freeze',
2954 'fsfreeze-thaw',
2955 'fstrim',
2956 'network-get-interfaces',
2957 'get-vcpus',
2958 'get-fsinfo',
2959 'get-memory-blocks',
2960 'get-memory-block-info',
2961 'suspend-hybrid',
2962 'suspend-ram',
2963 'suspend-disk',
2964 'shutdown',
a5d5341c
DM
2965 ];
2966
d1a47427
WL
2967__PACKAGE__->register_method({
2968 name => 'agent',
2969 path => '{vmid}/agent',
2970 method => 'POST',
2971 protected => 1,
2972 proxyto => 'node',
2973 description => "Execute Qemu Guest Agent commands.",
2974 permissions => {
2975 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2976 },
2977 parameters => {
2978 additionalProperties => 0,
2979 properties => {
2980 node => get_standard_option('pve-node'),
f38c5e27
DM
2981 vmid => get_standard_option('pve-vmid', {
2982 completion => \&PVE::QemuServer::complete_vmid_running }),
d1a47427
WL
2983 command => {
2984 type => 'string',
2985 description => "The QGA command.",
a5d5341c 2986 enum => $guest_agent_commands,
c07a9e3d 2987 },
d1a47427
WL
2988 },
2989 },
57bdd459
DM
2990 returns => {
2991 type => 'object',
2992 description => "Returns an object with a single `result` property. The type of that
2993property depends on the executed command.",
2994 },
d1a47427
WL
2995 code => sub {
2996 my ($param) = @_;
2997
2998 my $vmid = $param->{vmid};
2999
3000 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
3001
d1a47427
WL
3002 die "No Qemu Guest Agent\n" if !defined($conf->{agent});
3003 die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
3004
249d8fed
DM
3005 my $cmd = $param->{command};
3006
3007 my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
d1a47427 3008
57bdd459 3009 return { result => $res };
d1a47427
WL
3010 }});
3011
0d02881c
AD
3012__PACKAGE__->register_method({
3013 name => 'resize_vm',
614e3941 3014 path => '{vmid}/resize',
0d02881c
AD
3015 method => 'PUT',
3016 protected => 1,
3017 proxyto => 'node',
2f48a4f5 3018 description => "Extend volume size.",
0d02881c 3019 permissions => {
3b2773f6 3020 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3021 },
3022 parameters => {
3023 additionalProperties => 0,
2f48a4f5
DM
3024 properties => {
3025 node => get_standard_option('pve-node'),
335af808 3026 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3027 skiplock => get_standard_option('skiplock'),
3028 disk => {
3029 type => 'string',
3030 description => "The disk you want to resize.",
74479ee9 3031 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3032 },
3033 size => {
3034 type => 'string',
f91b2e45 3035 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3036 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
3037 },
3038 digest => {
3039 type => 'string',
3040 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3041 maxLength => 40,
3042 optional => 1,
3043 },
3044 },
0d02881c
AD
3045 },
3046 returns => { type => 'null'},
3047 code => sub {
3048 my ($param) = @_;
3049
3050 my $rpcenv = PVE::RPCEnvironment::get();
3051
3052 my $authuser = $rpcenv->get_user();
3053
3054 my $node = extract_param($param, 'node');
3055
3056 my $vmid = extract_param($param, 'vmid');
3057
3058 my $digest = extract_param($param, 'digest');
3059
2f48a4f5 3060 my $disk = extract_param($param, 'disk');
75466c4f 3061
2f48a4f5 3062 my $sizestr = extract_param($param, 'size');
0d02881c 3063
f91b2e45 3064 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3065 raise_param_exc({ skiplock => "Only root may use this option." })
3066 if $skiplock && $authuser ne 'root@pam';
3067
0d02881c
AD
3068 my $storecfg = PVE::Storage::config();
3069
0d02881c
AD
3070 my $updatefn = sub {
3071
ffda963f 3072 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3073
3074 die "checksum missmatch (file change by other user?)\n"
3075 if $digest && $digest ne $conf->{digest};
ffda963f 3076 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3077
f91b2e45
DM
3078 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3079
3080 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3081
d662790a
WL
3082 my (undef, undef, undef, undef, undef, undef, $format) =
3083 PVE::Storage::parse_volname($storecfg, $drive->{file});
3084
3085 die "can't resize volume: $disk if snapshot exists\n"
3086 if %{$conf->{snapshots}} && $format eq 'qcow2';
3087
f91b2e45
DM
3088 my $volid = $drive->{file};
3089
3090 die "disk '$disk' has no associated volume\n" if !$volid;
3091
3092 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3093
3094 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3095
3096 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3097
b572a606 3098 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3099 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3100
3101 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3102 my ($ext, $newsize, $unit) = ($1, $2, $4);
3103 if ($unit) {
3104 if ($unit eq 'K') {
3105 $newsize = $newsize * 1024;
3106 } elsif ($unit eq 'M') {
3107 $newsize = $newsize * 1024 * 1024;
3108 } elsif ($unit eq 'G') {
3109 $newsize = $newsize * 1024 * 1024 * 1024;
3110 } elsif ($unit eq 'T') {
3111 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3112 }
3113 }
3114 $newsize += $size if $ext;
3115 $newsize = int($newsize);
3116
9a478b17 3117 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3118
3119 return if $size == $newsize;
3120
2f48a4f5 3121 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3122
f91b2e45 3123 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3124
f91b2e45
DM
3125 $drive->{size} = $newsize;
3126 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3127
ffda963f 3128 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3129 };
0d02881c 3130
ffda963f 3131 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3132 return undef;
3133 }});
3134
9dbd1ee4 3135__PACKAGE__->register_method({
7e7d7b61 3136 name => 'snapshot_list',
9dbd1ee4 3137 path => '{vmid}/snapshot',
7e7d7b61
DM
3138 method => 'GET',
3139 description => "List all snapshots.",
3140 permissions => {
3141 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3142 },
3143 proxyto => 'node',
3144 protected => 1, # qemu pid files are only readable by root
3145 parameters => {
3146 additionalProperties => 0,
3147 properties => {
e261de40 3148 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3149 node => get_standard_option('pve-node'),
3150 },
3151 },
3152 returns => {
3153 type => 'array',
3154 items => {
3155 type => "object",
3156 properties => {},
3157 },
3158 links => [ { rel => 'child', href => "{name}" } ],
3159 },
3160 code => sub {
3161 my ($param) = @_;
3162
6aa4651b
DM
3163 my $vmid = $param->{vmid};
3164
ffda963f 3165 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3166 my $snaphash = $conf->{snapshots} || {};
3167
3168 my $res = [];
3169
3170 foreach my $name (keys %$snaphash) {
0ea6bc69 3171 my $d = $snaphash->{$name};
75466c4f
DM
3172 my $item = {
3173 name => $name,
3174 snaptime => $d->{snaptime} || 0,
6aa4651b 3175 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3176 description => $d->{description} || '',
3177 };
0ea6bc69 3178 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3179 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3180 push @$res, $item;
3181 }
3182
6aa4651b
DM
3183 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
3184 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
3185 $current->{parent} = $conf->{parent} if $conf->{parent};
3186
3187 push @$res, $current;
7e7d7b61
DM
3188
3189 return $res;
3190 }});
3191
3192__PACKAGE__->register_method({
3193 name => 'snapshot',
3194 path => '{vmid}/snapshot',
3195 method => 'POST',
9dbd1ee4
AD
3196 protected => 1,
3197 proxyto => 'node',
3198 description => "Snapshot a VM.",
3199 permissions => {
f1baf1df 3200 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3201 },
3202 parameters => {
3203 additionalProperties => 0,
3204 properties => {
3205 node => get_standard_option('pve-node'),
335af808 3206 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3207 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3208 vmstate => {
3209 optional => 1,
3210 type => 'boolean',
3211 description => "Save the vmstate",
3212 },
782f4f75
DM
3213 description => {
3214 optional => 1,
3215 type => 'string',
3216 description => "A textual description or comment.",
3217 },
9dbd1ee4
AD
3218 },
3219 },
7e7d7b61
DM
3220 returns => {
3221 type => 'string',
3222 description => "the task ID.",
3223 },
9dbd1ee4
AD
3224 code => sub {
3225 my ($param) = @_;
3226
3227 my $rpcenv = PVE::RPCEnvironment::get();
3228
3229 my $authuser = $rpcenv->get_user();
3230
3231 my $node = extract_param($param, 'node');
3232
3233 my $vmid = extract_param($param, 'vmid');
3234
9dbd1ee4
AD
3235 my $snapname = extract_param($param, 'snapname');
3236
d1914468
DM
3237 die "unable to use snapshot name 'current' (reserved name)\n"
3238 if $snapname eq 'current';
3239
7e7d7b61 3240 my $realcmd = sub {
22c377f0 3241 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
b2c9558d 3242 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3243 $param->{description});
7e7d7b61
DM
3244 };
3245
3246 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3247 }});
3248
154ccdcd
DM
3249__PACKAGE__->register_method({
3250 name => 'snapshot_cmd_idx',
3251 path => '{vmid}/snapshot/{snapname}',
3252 description => '',
3253 method => 'GET',
3254 permissions => {
3255 user => 'all',
3256 },
3257 parameters => {
3258 additionalProperties => 0,
3259 properties => {
3260 vmid => get_standard_option('pve-vmid'),
3261 node => get_standard_option('pve-node'),
8abd398b 3262 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3263 },
3264 },
3265 returns => {
3266 type => 'array',
3267 items => {
3268 type => "object",
3269 properties => {},
3270 },
3271 links => [ { rel => 'child', href => "{cmd}" } ],
3272 },
3273 code => sub {
3274 my ($param) = @_;
3275
3276 my $res = [];
3277
3278 push @$res, { cmd => 'rollback' };
d788cea6 3279 push @$res, { cmd => 'config' };
154ccdcd
DM
3280
3281 return $res;
3282 }});
3283
d788cea6
DM
3284__PACKAGE__->register_method({
3285 name => 'update_snapshot_config',
3286 path => '{vmid}/snapshot/{snapname}/config',
3287 method => 'PUT',
3288 protected => 1,
3289 proxyto => 'node',
3290 description => "Update snapshot metadata.",
3291 permissions => {
3292 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3293 },
3294 parameters => {
3295 additionalProperties => 0,
3296 properties => {
3297 node => get_standard_option('pve-node'),
3298 vmid => get_standard_option('pve-vmid'),
3299 snapname => get_standard_option('pve-snapshot-name'),
3300 description => {
3301 optional => 1,
3302 type => 'string',
3303 description => "A textual description or comment.",
3304 },
3305 },
3306 },
3307 returns => { type => 'null' },
3308 code => sub {
3309 my ($param) = @_;
3310
3311 my $rpcenv = PVE::RPCEnvironment::get();
3312
3313 my $authuser = $rpcenv->get_user();
3314
3315 my $vmid = extract_param($param, 'vmid');
3316
3317 my $snapname = extract_param($param, 'snapname');
3318
3319 return undef if !defined($param->{description});
3320
3321 my $updatefn = sub {
3322
ffda963f 3323 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3324
ffda963f 3325 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3326
3327 my $snap = $conf->{snapshots}->{$snapname};
3328
75466c4f
DM
3329 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3330
d788cea6
DM
3331 $snap->{description} = $param->{description} if defined($param->{description});
3332
ffda963f 3333 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3334 };
3335
ffda963f 3336 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3337
3338 return undef;
3339 }});
3340
3341__PACKAGE__->register_method({
3342 name => 'get_snapshot_config',
3343 path => '{vmid}/snapshot/{snapname}/config',
3344 method => 'GET',
3345 proxyto => 'node',
3346 description => "Get snapshot configuration",
3347 permissions => {
3348 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3349 },
3350 parameters => {
3351 additionalProperties => 0,
3352 properties => {
3353 node => get_standard_option('pve-node'),
3354 vmid => get_standard_option('pve-vmid'),
3355 snapname => get_standard_option('pve-snapshot-name'),
3356 },
3357 },
3358 returns => { type => "object" },
3359 code => sub {
3360 my ($param) = @_;
3361
3362 my $rpcenv = PVE::RPCEnvironment::get();
3363
3364 my $authuser = $rpcenv->get_user();
3365
3366 my $vmid = extract_param($param, 'vmid');
3367
3368 my $snapname = extract_param($param, 'snapname');
3369
ffda963f 3370 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3371
3372 my $snap = $conf->{snapshots}->{$snapname};
3373
75466c4f
DM
3374 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3375
d788cea6
DM
3376 return $snap;
3377 }});
3378
7e7d7b61
DM
3379__PACKAGE__->register_method({
3380 name => 'rollback',
154ccdcd 3381 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3382 method => 'POST',
3383 protected => 1,
3384 proxyto => 'node',
3385 description => "Rollback VM state to specified snapshot.",
3386 permissions => {
f1baf1df 3387 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3388 },
3389 parameters => {
3390 additionalProperties => 0,
3391 properties => {
3392 node => get_standard_option('pve-node'),
335af808 3393 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3394 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3395 },
3396 },
3397 returns => {
3398 type => 'string',
3399 description => "the task ID.",
3400 },
3401 code => sub {
3402 my ($param) = @_;
3403
3404 my $rpcenv = PVE::RPCEnvironment::get();
3405
3406 my $authuser = $rpcenv->get_user();
3407
3408 my $node = extract_param($param, 'node');
3409
3410 my $vmid = extract_param($param, 'vmid');
3411
3412 my $snapname = extract_param($param, 'snapname');
3413
7e7d7b61 3414 my $realcmd = sub {
22c377f0 3415 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3416 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3417 };
3418
c068c1c3
WL
3419 my $worker = sub {
3420 # hold migration lock, this makes sure that nobody create replication snapshots
3421 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3422 };
3423
3424 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3425 }});
3426
3427__PACKAGE__->register_method({
3428 name => 'delsnapshot',
3429 path => '{vmid}/snapshot/{snapname}',
3430 method => 'DELETE',
3431 protected => 1,
3432 proxyto => 'node',
3433 description => "Delete a VM snapshot.",
3434 permissions => {
f1baf1df 3435 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3436 },
3437 parameters => {
3438 additionalProperties => 0,
3439 properties => {
3440 node => get_standard_option('pve-node'),
335af808 3441 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3442 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3443 force => {
3444 optional => 1,
3445 type => 'boolean',
3446 description => "For removal from config file, even if removing disk snapshots fails.",
3447 },
7e7d7b61
DM
3448 },
3449 },
3450 returns => {
3451 type => 'string',
3452 description => "the task ID.",
3453 },
3454 code => sub {
3455 my ($param) = @_;
3456
3457 my $rpcenv = PVE::RPCEnvironment::get();
3458
3459 my $authuser = $rpcenv->get_user();
3460
3461 my $node = extract_param($param, 'node');
3462
3463 my $vmid = extract_param($param, 'vmid');
3464
3465 my $snapname = extract_param($param, 'snapname');
3466
7e7d7b61 3467 my $realcmd = sub {
22c377f0 3468 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3469 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3470 };
9dbd1ee4 3471
7b2257a8 3472 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3473 }});
3474
04a69bb4
AD
3475__PACKAGE__->register_method({
3476 name => 'template',
3477 path => '{vmid}/template',
3478 method => 'POST',
3479 protected => 1,
3480 proxyto => 'node',
3481 description => "Create a Template.",
b02691d8 3482 permissions => {
7af0a6c8
DM
3483 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3484 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3485 },
04a69bb4
AD
3486 parameters => {
3487 additionalProperties => 0,
3488 properties => {
3489 node => get_standard_option('pve-node'),
335af808 3490 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3491 disk => {
3492 optional => 1,
3493 type => 'string',
3494 description => "If you want to convert only 1 disk to base image.",
74479ee9 3495 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3496 },
3497
3498 },
3499 },
3500 returns => { type => 'null'},
3501 code => sub {
3502 my ($param) = @_;
3503
3504 my $rpcenv = PVE::RPCEnvironment::get();
3505
3506 my $authuser = $rpcenv->get_user();
3507
3508 my $node = extract_param($param, 'node');
3509
3510 my $vmid = extract_param($param, 'vmid');
3511
3512 my $disk = extract_param($param, 'disk');
3513
3514 my $updatefn = sub {
3515
ffda963f 3516 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3517
ffda963f 3518 PVE::QemuConfig->check_lock($conf);
04a69bb4 3519
75466c4f 3520 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3521 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3522
75466c4f 3523 die "you can't convert a template to a template\n"
ffda963f 3524 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3525
75466c4f 3526 die "you can't convert a VM to template if VM is running\n"
218cab9a 3527 if PVE::QemuServer::check_running($vmid);
35c5fdef 3528
04a69bb4
AD
3529 my $realcmd = sub {
3530 PVE::QemuServer::template_create($vmid, $conf, $disk);
3531 };
04a69bb4 3532
75e7e997 3533 $conf->{template} = 1;
ffda963f 3534 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3535
3536 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3537 };
3538
ffda963f 3539 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3540 return undef;
3541 }});
3542
1e3baf05 35431;