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