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