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