]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
remove netcat6 dependency
[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]?',
2f48a4f5
DM
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.",
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;