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