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