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