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