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