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