]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
add agent flag to vm status api call
[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
ccb88f45 1433 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga}, '-escape', '0' ];
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
ccb88f45 1561 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
87302002
DC
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
c9a074b8
DC
1763 $status->{agent} = 1 if $conf->{agent};
1764
8610701a 1765 return $status;
1e3baf05
DM
1766 }});
1767
1768__PACKAGE__->register_method({
afdb31d5 1769 name => 'vm_start',
5fdbe4f0
DM
1770 path => '{vmid}/status/start',
1771 method => 'POST',
1e3baf05
DM
1772 protected => 1,
1773 proxyto => 'node',
5fdbe4f0 1774 description => "Start virtual machine.",
a0d1b1a2
DM
1775 permissions => {
1776 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1777 },
1e3baf05
DM
1778 parameters => {
1779 additionalProperties => 0,
1780 properties => {
1781 node => get_standard_option('pve-node'),
ab5904f7
TL
1782 vmid => get_standard_option('pve-vmid',
1783 { completion => \&PVE::QemuServer::complete_vmid_stopped }),
3ea94c60
DM
1784 skiplock => get_standard_option('skiplock'),
1785 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1786 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
2de2d6f7
TL
1787 migration_type => {
1788 type => 'string',
1789 enum => ['secure', 'insecure'],
1790 description => "Migration traffic is encrypted using an SSH " .
1791 "tunnel by default. On secure, completely private networks " .
1792 "this can be disabled to increase performance.",
1793 optional => 1,
1794 },
1795 migration_network => {
29ddbe70 1796 type => 'string', format => 'CIDR',
2de2d6f7
TL
1797 description => "CIDR of the (sub) network that is used for migration.",
1798 optional => 1,
1799 },
952958bc 1800 machine => get_standard_option('pve-qm-machine'),
2189246c 1801 targetstorage => {
bd2d5fe6 1802 description => "Target storage for the migration. (Can be '1' to use the same storage id as on the source node.)",
2189246c
AD
1803 type => 'string',
1804 optional => 1
1805 }
1e3baf05
DM
1806 },
1807 },
afdb31d5 1808 returns => {
5fdbe4f0
DM
1809 type => 'string',
1810 },
1e3baf05
DM
1811 code => sub {
1812 my ($param) = @_;
1813
1814 my $rpcenv = PVE::RPCEnvironment::get();
1815
a0d1b1a2 1816 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1817
1818 my $node = extract_param($param, 'node');
1819
1e3baf05
DM
1820 my $vmid = extract_param($param, 'vmid');
1821
952958bc
DM
1822 my $machine = extract_param($param, 'machine');
1823
3ea94c60 1824 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1825 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1826 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1827
1e3baf05 1828 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1829 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1830 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1831
7e8dcf2c
AD
1832 my $migratedfrom = extract_param($param, 'migratedfrom');
1833 raise_param_exc({ migratedfrom => "Only root may use this option." })
1834 if $migratedfrom && $authuser ne 'root@pam';
1835
2de2d6f7
TL
1836 my $migration_type = extract_param($param, 'migration_type');
1837 raise_param_exc({ migration_type => "Only root may use this option." })
1838 if $migration_type && $authuser ne 'root@pam';
1839
1840 my $migration_network = extract_param($param, 'migration_network');
1841 raise_param_exc({ migration_network => "Only root may use this option." })
1842 if $migration_network && $authuser ne 'root@pam';
1843
2189246c
AD
1844 my $targetstorage = extract_param($param, 'targetstorage');
1845 raise_param_exc({ targetstorage => "Only root may use this option." })
1846 if $targetstorage && $authuser ne 'root@pam';
1847
1848 raise_param_exc({ targetstorage => "targetstorage can only by used with migratedfrom." })
1849 if $targetstorage && !$migratedfrom;
1850
7c14dcae
DM
1851 # read spice ticket from STDIN
1852 my $spice_ticket;
1853 if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
e5caa02e 1854 if (defined(my $line = <STDIN>)) {
760fb3c8
DM
1855 chomp $line;
1856 $spice_ticket = $line;
1857 }
7c14dcae
DM
1858 }
1859
98cbd0f4
WB
1860 PVE::Cluster::check_cfs_quorum();
1861
afdb31d5 1862 my $storecfg = PVE::Storage::config();
5fdbe4f0 1863
2003f0f8 1864 if (PVE::HA::Config::vm_is_ha_managed($vmid) && !$stateuri &&
cce37749 1865 $rpcenv->{type} ne 'ha') {
5fdbe4f0 1866
88fc87b4
DM
1867 my $hacmd = sub {
1868 my $upid = shift;
5fdbe4f0 1869
c44291cd 1870 my $service = "vm:$vmid";
5fdbe4f0 1871
2a7e2b82 1872 my $cmd = ['ha-manager', 'set', $service, '--state', 'started'];
88fc87b4 1873
02765844 1874 print "Requesting HA start for VM $vmid\n";
88fc87b4
DM
1875
1876 PVE::Tools::run_command($cmd);
1877
1878 return;
1879 };
1880
1881 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1882
1883 } else {
1884
1885 my $realcmd = sub {
1886 my $upid = shift;
1887
1888 syslog('info', "start VM $vmid: $upid\n");
1889
fa8ea931 1890 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef,
2189246c 1891 $machine, $spice_ticket, $migration_network, $migration_type, $targetstorage);
88fc87b4
DM
1892
1893 return;
1894 };
5fdbe4f0 1895
88fc87b4
DM
1896 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1897 }
5fdbe4f0
DM
1898 }});
1899
1900__PACKAGE__->register_method({
afdb31d5 1901 name => 'vm_stop',
5fdbe4f0
DM
1902 path => '{vmid}/status/stop',
1903 method => 'POST',
1904 protected => 1,
1905 proxyto => 'node',
346130b2 1906 description => "Stop virtual machine. The qemu process will exit immediately. This" .
d6c747ff 1907 "is akin to pulling the power plug of a running computer and may damage the VM data",
a0d1b1a2
DM
1908 permissions => {
1909 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1910 },
5fdbe4f0
DM
1911 parameters => {
1912 additionalProperties => 0,
1913 properties => {
1914 node => get_standard_option('pve-node'),
ab5904f7
TL
1915 vmid => get_standard_option('pve-vmid',
1916 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 1917 skiplock => get_standard_option('skiplock'),
debe8882 1918 migratedfrom => get_standard_option('pve-node', { optional => 1 }),
c6bb9502
DM
1919 timeout => {
1920 description => "Wait maximal timeout seconds.",
1921 type => 'integer',
1922 minimum => 0,
1923 optional => 1,
254575e9
DM
1924 },
1925 keepActive => {
94a17e1d 1926 description => "Do not deactivate storage volumes.",
254575e9
DM
1927 type => 'boolean',
1928 optional => 1,
1929 default => 0,
c6bb9502 1930 }
5fdbe4f0
DM
1931 },
1932 },
afdb31d5 1933 returns => {
5fdbe4f0
DM
1934 type => 'string',
1935 },
1936 code => sub {
1937 my ($param) = @_;
1938
1939 my $rpcenv = PVE::RPCEnvironment::get();
1940
a0d1b1a2 1941 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1942
1943 my $node = extract_param($param, 'node');
1944
1945 my $vmid = extract_param($param, 'vmid');
1946
1947 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1948 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1949 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1950
254575e9 1951 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1952 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1953 if $keepActive && $authuser ne 'root@pam';
254575e9 1954
af30308f
DM
1955 my $migratedfrom = extract_param($param, 'migratedfrom');
1956 raise_param_exc({ migratedfrom => "Only root may use this option." })
1957 if $migratedfrom && $authuser ne 'root@pam';
1958
1959
ff1a2432
DM
1960 my $storecfg = PVE::Storage::config();
1961
2003f0f8 1962 if (PVE::HA::Config::vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
5fdbe4f0 1963
88fc87b4
DM
1964 my $hacmd = sub {
1965 my $upid = shift;
5fdbe4f0 1966
c44291cd 1967 my $service = "vm:$vmid";
c6bb9502 1968
e0feef86 1969 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
88fc87b4 1970
02765844 1971 print "Requesting HA stop for VM $vmid\n";
88fc87b4
DM
1972
1973 PVE::Tools::run_command($cmd);
5fdbe4f0 1974
88fc87b4
DM
1975 return;
1976 };
1977
1978 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1979
1980 } else {
1981 my $realcmd = sub {
1982 my $upid = shift;
1983
1984 syslog('info', "stop VM $vmid: $upid\n");
1985
1986 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 1987 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
1988
1989 return;
1990 };
1991
1992 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1993 }
5fdbe4f0
DM
1994 }});
1995
1996__PACKAGE__->register_method({
afdb31d5 1997 name => 'vm_reset',
5fdbe4f0
DM
1998 path => '{vmid}/status/reset',
1999 method => 'POST',
2000 protected => 1,
2001 proxyto => 'node',
2002 description => "Reset virtual machine.",
a0d1b1a2
DM
2003 permissions => {
2004 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2005 },
5fdbe4f0
DM
2006 parameters => {
2007 additionalProperties => 0,
2008 properties => {
2009 node => get_standard_option('pve-node'),
ab5904f7
TL
2010 vmid => get_standard_option('pve-vmid',
2011 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2012 skiplock => get_standard_option('skiplock'),
2013 },
2014 },
afdb31d5 2015 returns => {
5fdbe4f0
DM
2016 type => 'string',
2017 },
2018 code => sub {
2019 my ($param) = @_;
2020
2021 my $rpcenv = PVE::RPCEnvironment::get();
2022
a0d1b1a2 2023 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2024
2025 my $node = extract_param($param, 'node');
2026
2027 my $vmid = extract_param($param, 'vmid');
2028
2029 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2030 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2031 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2032
ff1a2432
DM
2033 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2034
5fdbe4f0
DM
2035 my $realcmd = sub {
2036 my $upid = shift;
2037
1e3baf05 2038 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
2039
2040 return;
2041 };
2042
a0d1b1a2 2043 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2044 }});
2045
2046__PACKAGE__->register_method({
afdb31d5 2047 name => 'vm_shutdown',
5fdbe4f0
DM
2048 path => '{vmid}/status/shutdown',
2049 method => 'POST',
2050 protected => 1,
2051 proxyto => 'node',
d6c747ff
EK
2052 description => "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2053 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
a0d1b1a2
DM
2054 permissions => {
2055 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2056 },
5fdbe4f0
DM
2057 parameters => {
2058 additionalProperties => 0,
2059 properties => {
2060 node => get_standard_option('pve-node'),
ab5904f7
TL
2061 vmid => get_standard_option('pve-vmid',
2062 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2063 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
2064 timeout => {
2065 description => "Wait maximal timeout seconds.",
2066 type => 'integer',
2067 minimum => 0,
2068 optional => 1,
9269013a
DM
2069 },
2070 forceStop => {
2071 description => "Make sure the VM stops.",
2072 type => 'boolean',
2073 optional => 1,
2074 default => 0,
254575e9
DM
2075 },
2076 keepActive => {
94a17e1d 2077 description => "Do not deactivate storage volumes.",
254575e9
DM
2078 type => 'boolean',
2079 optional => 1,
2080 default => 0,
c6bb9502 2081 }
5fdbe4f0
DM
2082 },
2083 },
afdb31d5 2084 returns => {
5fdbe4f0
DM
2085 type => 'string',
2086 },
2087 code => sub {
2088 my ($param) = @_;
2089
2090 my $rpcenv = PVE::RPCEnvironment::get();
2091
a0d1b1a2 2092 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2093
2094 my $node = extract_param($param, 'node');
2095
2096 my $vmid = extract_param($param, 'vmid');
2097
2098 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2099 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2100 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2101
254575e9 2102 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 2103 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 2104 if $keepActive && $authuser ne 'root@pam';
254575e9 2105
02d07cf5
DM
2106 my $storecfg = PVE::Storage::config();
2107
89897367
DC
2108 my $shutdown = 1;
2109
2110 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2111 # otherwise, we will infer a shutdown command, but run into the timeout,
2112 # then when the vm is resumed, it will instantly shutdown
2113 #
2114 # checking the qmp status here to get feedback to the gui/cli/api
2115 # and the status query should not take too long
2116 my $qmpstatus;
2117 eval {
2118 $qmpstatus = PVE::QemuServer::vm_qmp_command($vmid, { execute => "query-status" }, 0);
2119 };
2120 my $err = $@ if $@;
2121
2122 if (!$err && $qmpstatus->{status} eq "paused") {
2123 if ($param->{forceStop}) {
2124 warn "VM is paused - stop instead of shutdown\n";
2125 $shutdown = 0;
2126 } else {
2127 die "VM is paused - cannot shutdown\n";
2128 }
2129 }
2130
ae849692
DM
2131 if (PVE::HA::Config::vm_is_ha_managed($vmid) &&
2132 ($rpcenv->{type} ne 'ha')) {
5fdbe4f0 2133
ae849692
DM
2134 my $hacmd = sub {
2135 my $upid = shift;
5fdbe4f0 2136
ae849692 2137 my $service = "vm:$vmid";
c6bb9502 2138
ae849692
DM
2139 my $cmd = ['ha-manager', 'set', $service, '--state', 'stopped'];
2140
02765844 2141 print "Requesting HA stop for VM $vmid\n";
ae849692
DM
2142
2143 PVE::Tools::run_command($cmd);
2144
2145 return;
2146 };
2147
2148 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2149
2150 } else {
2151
2152 my $realcmd = sub {
2153 my $upid = shift;
2154
2155 syslog('info', "shutdown VM $vmid: $upid\n");
5fdbe4f0 2156
ae849692
DM
2157 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
2158 $shutdown, $param->{forceStop}, $keepActive);
2159
2160 return;
2161 };
2162
2163 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2164 }
5fdbe4f0
DM
2165 }});
2166
2167__PACKAGE__->register_method({
afdb31d5 2168 name => 'vm_suspend',
5fdbe4f0
DM
2169 path => '{vmid}/status/suspend',
2170 method => 'POST',
2171 protected => 1,
2172 proxyto => 'node',
2173 description => "Suspend virtual machine.",
a0d1b1a2
DM
2174 permissions => {
2175 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2176 },
5fdbe4f0
DM
2177 parameters => {
2178 additionalProperties => 0,
2179 properties => {
2180 node => get_standard_option('pve-node'),
ab5904f7
TL
2181 vmid => get_standard_option('pve-vmid',
2182 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2183 skiplock => get_standard_option('skiplock'),
2184 },
2185 },
afdb31d5 2186 returns => {
5fdbe4f0
DM
2187 type => 'string',
2188 },
2189 code => sub {
2190 my ($param) = @_;
2191
2192 my $rpcenv = PVE::RPCEnvironment::get();
2193
a0d1b1a2 2194 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2195
2196 my $node = extract_param($param, 'node');
2197
2198 my $vmid = extract_param($param, 'vmid');
2199
2200 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2201 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2202 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2203
ff1a2432
DM
2204 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
2205
5fdbe4f0
DM
2206 my $realcmd = sub {
2207 my $upid = shift;
2208
2209 syslog('info', "suspend VM $vmid: $upid\n");
2210
1e3baf05 2211 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
2212
2213 return;
2214 };
2215
a0d1b1a2 2216 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2217 }});
2218
2219__PACKAGE__->register_method({
afdb31d5 2220 name => 'vm_resume',
5fdbe4f0
DM
2221 path => '{vmid}/status/resume',
2222 method => 'POST',
2223 protected => 1,
2224 proxyto => 'node',
2225 description => "Resume virtual machine.",
a0d1b1a2
DM
2226 permissions => {
2227 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2228 },
5fdbe4f0
DM
2229 parameters => {
2230 additionalProperties => 0,
2231 properties => {
2232 node => get_standard_option('pve-node'),
ab5904f7
TL
2233 vmid => get_standard_option('pve-vmid',
2234 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0 2235 skiplock => get_standard_option('skiplock'),
289e0b85
AD
2236 nocheck => { type => 'boolean', optional => 1 },
2237
5fdbe4f0
DM
2238 },
2239 },
afdb31d5 2240 returns => {
5fdbe4f0
DM
2241 type => 'string',
2242 },
2243 code => sub {
2244 my ($param) = @_;
2245
2246 my $rpcenv = PVE::RPCEnvironment::get();
2247
a0d1b1a2 2248 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2249
2250 my $node = extract_param($param, 'node');
2251
2252 my $vmid = extract_param($param, 'vmid');
2253
2254 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2255 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2256 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 2257
289e0b85
AD
2258 my $nocheck = extract_param($param, 'nocheck');
2259
2260 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid, $nocheck);
ff1a2432 2261
5fdbe4f0
DM
2262 my $realcmd = sub {
2263 my $upid = shift;
2264
2265 syslog('info', "resume VM $vmid: $upid\n");
2266
289e0b85 2267 PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
1e3baf05 2268
5fdbe4f0
DM
2269 return;
2270 };
2271
a0d1b1a2 2272 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
2273 }});
2274
2275__PACKAGE__->register_method({
afdb31d5 2276 name => 'vm_sendkey',
5fdbe4f0
DM
2277 path => '{vmid}/sendkey',
2278 method => 'PUT',
2279 protected => 1,
2280 proxyto => 'node',
2281 description => "Send key event to virtual machine.",
a0d1b1a2
DM
2282 permissions => {
2283 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2284 },
5fdbe4f0
DM
2285 parameters => {
2286 additionalProperties => 0,
2287 properties => {
2288 node => get_standard_option('pve-node'),
ab5904f7
TL
2289 vmid => get_standard_option('pve-vmid',
2290 { completion => \&PVE::QemuServer::complete_vmid_running }),
5fdbe4f0
DM
2291 skiplock => get_standard_option('skiplock'),
2292 key => {
2293 description => "The key (qemu monitor encoding).",
2294 type => 'string'
2295 }
2296 },
2297 },
2298 returns => { type => 'null'},
2299 code => sub {
2300 my ($param) = @_;
2301
2302 my $rpcenv = PVE::RPCEnvironment::get();
2303
a0d1b1a2 2304 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
2305
2306 my $node = extract_param($param, 'node');
2307
2308 my $vmid = extract_param($param, 'vmid');
2309
2310 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 2311 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 2312 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
2313
2314 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
2315
2316 return;
1e3baf05
DM
2317 }});
2318
1ac0d2ee
AD
2319__PACKAGE__->register_method({
2320 name => 'vm_feature',
2321 path => '{vmid}/feature',
2322 method => 'GET',
2323 proxyto => 'node',
75466c4f 2324 protected => 1,
1ac0d2ee
AD
2325 description => "Check if feature for virtual machine is available.",
2326 permissions => {
2327 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2328 },
2329 parameters => {
2330 additionalProperties => 0,
2331 properties => {
2332 node => get_standard_option('pve-node'),
2333 vmid => get_standard_option('pve-vmid'),
2334 feature => {
2335 description => "Feature to check.",
2336 type => 'string',
7758ce86 2337 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
2338 },
2339 snapname => get_standard_option('pve-snapshot-name', {
2340 optional => 1,
2341 }),
2342 },
1ac0d2ee
AD
2343 },
2344 returns => {
719893a9
DM
2345 type => "object",
2346 properties => {
2347 hasFeature => { type => 'boolean' },
7043d946 2348 nodes => {
719893a9
DM
2349 type => 'array',
2350 items => { type => 'string' },
2351 }
2352 },
1ac0d2ee
AD
2353 },
2354 code => sub {
2355 my ($param) = @_;
2356
2357 my $node = extract_param($param, 'node');
2358
2359 my $vmid = extract_param($param, 'vmid');
2360
2361 my $snapname = extract_param($param, 'snapname');
2362
2363 my $feature = extract_param($param, 'feature');
2364
2365 my $running = PVE::QemuServer::check_running($vmid);
2366
ffda963f 2367 my $conf = PVE::QemuConfig->load_config($vmid);
1ac0d2ee
AD
2368
2369 if($snapname){
2370 my $snap = $conf->{snapshots}->{$snapname};
2371 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2372 $conf = $snap;
2373 }
2374 my $storecfg = PVE::Storage::config();
2375
719893a9 2376 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
b2c9558d 2377 my $hasFeature = PVE::QemuConfig->has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2378
719893a9
DM
2379 return {
2380 hasFeature => $hasFeature,
2381 nodes => [ keys %$nodelist ],
7043d946 2382 };
1ac0d2ee
AD
2383 }});
2384
6116f729 2385__PACKAGE__->register_method({
9418baad
DM
2386 name => 'clone_vm',
2387 path => '{vmid}/clone',
6116f729
DM
2388 method => 'POST',
2389 protected => 1,
2390 proxyto => 'node',
37329185 2391 description => "Create a copy of virtual machine/template.",
6116f729 2392 permissions => {
9418baad 2393 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2394 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2395 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2396 check =>
2397 [ 'and',
9418baad 2398 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2399 [ 'or',
6116f729
DM
2400 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2401 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2402 ],
2403 ]
2404 },
2405 parameters => {
2406 additionalProperties => 0,
2407 properties => {
6116f729 2408 node => get_standard_option('pve-node'),
335af808 2409 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
9418baad 2410 newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }),
a60ab1a6
DM
2411 name => {
2412 optional => 1,
2413 type => 'string', format => 'dns-name',
2414 description => "Set a name for the new VM.",
2415 },
2416 description => {
2417 optional => 1,
2418 type => 'string',
2419 description => "Description for the new VM.",
2420 },
75466c4f 2421 pool => {
6116f729
DM
2422 optional => 1,
2423 type => 'string', format => 'pve-poolid',
2424 description => "Add the new VM to the specified pool.",
2425 },
9076d880 2426 snapname => get_standard_option('pve-snapshot-name', {
9076d880
DM
2427 optional => 1,
2428 }),
81f043eb 2429 storage => get_standard_option('pve-storage-id', {
9418baad 2430 description => "Target storage for full clone.",
4e4f83fe 2431 requires => 'full',
81f043eb
AD
2432 optional => 1,
2433 }),
55173c6b 2434 'format' => {
42a19c87
AD
2435 description => "Target format for file storage.",
2436 requires => 'full',
2437 type => 'string',
2438 optional => 1,
55173c6b 2439 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2440 },
6116f729
DM
2441 full => {
2442 optional => 1,
55173c6b
DM
2443 type => 'boolean',
2444 description => "Create a full copy of all disk. This is always done when " .
9418baad 2445 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729
DM
2446 default => 0,
2447 },
75466c4f 2448 target => get_standard_option('pve-node', {
55173c6b
DM
2449 description => "Target node. Only allowed if the original VM is on shared storage.",
2450 optional => 1,
2451 }),
2452 },
6116f729
DM
2453 },
2454 returns => {
2455 type => 'string',
2456 },
2457 code => sub {
2458 my ($param) = @_;
2459
2460 my $rpcenv = PVE::RPCEnvironment::get();
2461
55173c6b 2462 my $authuser = $rpcenv->get_user();
6116f729
DM
2463
2464 my $node = extract_param($param, 'node');
2465
2466 my $vmid = extract_param($param, 'vmid');
2467
2468 my $newid = extract_param($param, 'newid');
2469
6116f729
DM
2470 my $pool = extract_param($param, 'pool');
2471
2472 if (defined($pool)) {
2473 $rpcenv->check_pool_exist($pool);
2474 }
2475
55173c6b 2476 my $snapname = extract_param($param, 'snapname');
9076d880 2477
81f043eb
AD
2478 my $storage = extract_param($param, 'storage');
2479
42a19c87
AD
2480 my $format = extract_param($param, 'format');
2481
55173c6b
DM
2482 my $target = extract_param($param, 'target');
2483
2484 my $localnode = PVE::INotify::nodename();
2485
751cc556 2486 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2487
2488 PVE::Cluster::check_node_exists($target) if $target;
2489
6116f729
DM
2490 my $storecfg = PVE::Storage::config();
2491
4a5a2590
DM
2492 if ($storage) {
2493 # check if storage is enabled on local node
2494 PVE::Storage::storage_check_enabled($storecfg, $storage);
2495 if ($target) {
2496 # check if storage is available on target node
2497 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2498 # clone only works if target storage is shared
2499 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2500 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2501 }
2502 }
2503
55173c6b 2504 PVE::Cluster::check_cfs_quorum();
6116f729 2505
4e4f83fe
DM
2506 my $running = PVE::QemuServer::check_running($vmid) || 0;
2507
4e4f83fe
DM
2508 # exclusive lock if VM is running - else shared lock is enough;
2509 my $shared_lock = $running ? 0 : 1;
2510
9418baad 2511 my $clonefn = sub {
6116f729 2512
829967a9
DM
2513 # do all tests after lock
2514 # we also try to do all tests before we fork the worker
2515
ffda963f 2516 my $conf = PVE::QemuConfig->load_config($vmid);
6116f729 2517
ffda963f 2518 PVE::QemuConfig->check_lock($conf);
6116f729 2519
4e4f83fe 2520 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2521
4e4f83fe 2522 die "unexpected state change\n" if $verify_running != $running;
6116f729 2523
75466c4f
DM
2524 die "snapshot '$snapname' does not exist\n"
2525 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2526
75466c4f 2527 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2528
9418baad 2529 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2530
9418baad 2531 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2532
ffda963f 2533 my $conffile = PVE::QemuConfig->config_file($newid);
6116f729
DM
2534
2535 die "unable to create VM $newid: config file already exists\n"
2536 if -f $conffile;
2537
9418baad 2538 my $newconf = { lock => 'clone' };
829967a9 2539 my $drives = {};
34456bf0 2540 my $fullclone = {};
829967a9
DM
2541 my $vollist = [];
2542
2543 foreach my $opt (keys %$oldconf) {
2544 my $value = $oldconf->{$opt};
2545
2546 # do not copy snapshot related info
2547 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2548 $opt eq 'vmstate' || $opt eq 'snapstate';
2549
a78ea5df
WL
2550 # no need to copy unused images, because VMID(owner) changes anyways
2551 next if $opt =~ m/^unused\d+$/;
2552
829967a9
DM
2553 # always change MAC! address
2554 if ($opt =~ m/^net(\d+)$/) {
2555 my $net = PVE::QemuServer::parse_net($value);
b5b99790
WB
2556 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
2557 $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
829967a9 2558 $newconf->{$opt} = PVE::QemuServer::print_net($net);
74479ee9 2559 } elsif (PVE::QemuServer::is_valid_drivename($opt)) {
1f1412d1
DM
2560 my $drive = PVE::QemuServer::parse_drive($opt, $value);
2561 die "unable to parse drive options for '$opt'\n" if !$drive;
829967a9
DM
2562 if (PVE::QemuServer::drive_is_cdrom($drive)) {
2563 $newconf->{$opt} = $value; # simply copy configuration
2564 } else {
64ff6fe4 2565 if ($param->{full}) {
6318daca 2566 die "Full clone feature is not supported for drive '$opt'\n"
dba198b0 2567 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
34456bf0 2568 $fullclone->{$opt} = 1;
64ff6fe4
SP
2569 } else {
2570 # not full means clone instead of copy
6318daca 2571 die "Linked clone feature is not supported for drive '$opt'\n"
64ff6fe4 2572 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
dba198b0 2573 }
829967a9
DM
2574 $drives->{$opt} = $drive;
2575 push @$vollist, $drive->{file};
2576 }
2577 } else {
2578 # copy everything else
2579 $newconf->{$opt} = $value;
2580 }
2581 }
2582
cd11416f
DM
2583 # auto generate a new uuid
2584 my ($uuid, $uuid_str);
2585 UUID::generate($uuid);
2586 UUID::unparse($uuid, $uuid_str);
2587 my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
f34ebd52 2588 $smbios1->{uuid} = $uuid_str;
cd11416f
DM
2589 $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
2590
829967a9
DM
2591 delete $newconf->{template};
2592
2593 if ($param->{name}) {
2594 $newconf->{name} = $param->{name};
2595 } else {
c55fee03
DM
2596 if ($oldconf->{name}) {
2597 $newconf->{name} = "Copy-of-$oldconf->{name}";
2598 } else {
2599 $newconf->{name} = "Copy-of-VM-$vmid";
2600 }
829967a9 2601 }
2dd53043 2602
829967a9
DM
2603 if ($param->{description}) {
2604 $newconf->{description} = $param->{description};
2605 }
2606
6116f729 2607 # create empty/temp config - this fails if VM already exists on other node
9418baad 2608 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2609
2610 my $realcmd = sub {
2611 my $upid = shift;
2612
b83e0181 2613 my $newvollist = [];
c6fdd002 2614 my $jobs = {};
6116f729 2615
b83e0181 2616 eval {
eaae66be
TL
2617 local $SIG{INT} =
2618 local $SIG{TERM} =
2619 local $SIG{QUIT} =
2620 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2621
eb15b9f0 2622 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
6116f729 2623
c6fdd002
AD
2624 my $total_jobs = scalar(keys %{$drives});
2625 my $i = 1;
c6fdd002 2626
829967a9
DM
2627 foreach my $opt (keys %$drives) {
2628 my $drive = $drives->{$opt};
3b4cf0f0 2629 my $skipcomplete = ($total_jobs != $i); # finish after last drive
2dd53043 2630
152fe752 2631 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
3b4cf0f0
WB
2632 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
2633 $jobs, $skipcomplete, $oldconf->{agent});
00b095ca 2634
152fe752 2635 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2636
ffda963f 2637 PVE::QemuConfig->write_config($newid, $newconf);
c6fdd002 2638 $i++;
829967a9 2639 }
b83e0181
DM
2640
2641 delete $newconf->{lock};
ffda963f 2642 PVE::QemuConfig->write_config($newid, $newconf);
55173c6b
DM
2643
2644 if ($target) {
baca276d 2645 # always deactivate volumes - avoid lvm LVs to be active on several nodes
51eefb7e 2646 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
32acc380 2647 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
baca276d 2648
ffda963f 2649 my $newconffile = PVE::QemuConfig->config_file($newid, $target);
55173c6b
DM
2650 die "Failed to move config to node '$target' - rename failed: $!\n"
2651 if !rename($conffile, $newconffile);
2652 }
d703d4c0 2653
be517049 2654 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2655 };
75466c4f 2656 if (my $err = $@) {
6116f729
DM
2657 unlink $conffile;
2658
c6fdd002
AD
2659 eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
2660
b83e0181
DM
2661 sleep 1; # some storage like rbd need to wait before release volume - really?
2662
2663 foreach my $volid (@$newvollist) {
2664 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2665 warn $@ if $@;
2666 }
9418baad 2667 die "clone failed: $err";
6116f729
DM
2668 }
2669
2670 return;
2671 };
2672
457010cc
AG
2673 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
2674
9418baad 2675 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2676 };
2677
ffda963f 2678 return PVE::QemuConfig->lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2679 # Aquire exclusive lock lock for $newid
ffda963f 2680 return PVE::QemuConfig->lock_config_full($newid, 1, $clonefn);
6116f729
DM
2681 });
2682
2683 }});
2684
586bfa78 2685__PACKAGE__->register_method({
43bc02a9
DM
2686 name => 'move_vm_disk',
2687 path => '{vmid}/move_disk',
e2cd75fa 2688 method => 'POST',
586bfa78
AD
2689 protected => 1,
2690 proxyto => 'node',
2691 description => "Move volume to different storage.",
2692 permissions => {
c07a9e3d
DM
2693 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
2694 check => [ 'and',
2695 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2696 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2697 ],
586bfa78
AD
2698 },
2699 parameters => {
2700 additionalProperties => 0,
c07a9e3d 2701 properties => {
586bfa78 2702 node => get_standard_option('pve-node'),
335af808 2703 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
586bfa78
AD
2704 disk => {
2705 type => 'string',
2706 description => "The disk you want to move.",
74479ee9 2707 enum => [ PVE::QemuServer::valid_drive_names() ],
586bfa78 2708 },
335af808
DM
2709 storage => get_standard_option('pve-storage-id', {
2710 description => "Target storage.",
2711 completion => \&PVE::QemuServer::complete_storage,
2712 }),
635c3c44 2713 'format' => {
586bfa78
AD
2714 type => 'string',
2715 description => "Target Format.",
2716 enum => [ 'raw', 'qcow2', 'vmdk' ],
2717 optional => 1,
2718 },
70d45e33
DM
2719 delete => {
2720 type => 'boolean',
2721 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2722 optional => 1,
2723 default => 0,
2724 },
586bfa78
AD
2725 digest => {
2726 type => 'string',
2727 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2728 maxLength => 40,
2729 optional => 1,
2730 },
2731 },
2732 },
e2cd75fa
DM
2733 returns => {
2734 type => 'string',
2735 description => "the task ID.",
2736 },
586bfa78
AD
2737 code => sub {
2738 my ($param) = @_;
2739
2740 my $rpcenv = PVE::RPCEnvironment::get();
2741
2742 my $authuser = $rpcenv->get_user();
2743
2744 my $node = extract_param($param, 'node');
2745
2746 my $vmid = extract_param($param, 'vmid');
2747
2748 my $digest = extract_param($param, 'digest');
2749
2750 my $disk = extract_param($param, 'disk');
2751
2752 my $storeid = extract_param($param, 'storage');
2753
2754 my $format = extract_param($param, 'format');
2755
586bfa78
AD
2756 my $storecfg = PVE::Storage::config();
2757
2758 my $updatefn = sub {
2759
ffda963f 2760 my $conf = PVE::QemuConfig->load_config($vmid);
586bfa78 2761
dcce9b46
FG
2762 PVE::QemuConfig->check_lock($conf);
2763
586bfa78
AD
2764 die "checksum missmatch (file change by other user?)\n"
2765 if $digest && $digest ne $conf->{digest};
586bfa78
AD
2766
2767 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2768
2769 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2770
70d45e33 2771 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78
AD
2772
2773 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2774
e2cd75fa 2775 my $oldfmt;
70d45e33 2776 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
2777 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2778 $oldfmt = $1;
2779 }
2780
7043d946 2781 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 2782 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78 2783
9dbf9b54
FG
2784 # this only checks snapshots because $disk is passed!
2785 my $snapshotted = PVE::QemuServer::is_volume_in_use($storecfg, $conf, $disk, $old_volid);
2786 die "you can't move a disk with snapshots and delete the source\n"
2787 if $snapshotted && $param->{delete};
2788
586bfa78
AD
2789 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2790
2791 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
2792
2793 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
2794
586bfa78
AD
2795 my $realcmd = sub {
2796
2797 my $newvollist = [];
2798
2799 eval {
6cb0144a
EK
2800 local $SIG{INT} =
2801 local $SIG{TERM} =
2802 local $SIG{QUIT} =
2803 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
586bfa78 2804
9dbf9b54
FG
2805 warn "moving disk with snapshots, snapshots will not be moved!\n"
2806 if $snapshotted;
2807
e2cd75fa
DM
2808 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2809 $vmid, $storeid, $format, 1, $newvollist);
2810
2811 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2812
8793d495 2813 PVE::QemuConfig->add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2814
fbd7dcce
FG
2815 # convert moved disk to base if part of template
2816 PVE::QemuServer::template_create($vmid, $conf, $disk)
2817 if PVE::QemuConfig->is_template($conf);
2818
ffda963f 2819 PVE::QemuConfig->write_config($vmid, $conf);
73272365 2820
f34ebd52 2821 eval {
73272365 2822 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
f34ebd52 2823 PVE::Storage::deactivate_volumes($storecfg, [ $newdrive->{file} ])
73272365
DM
2824 if !$running;
2825 };
2826 warn $@ if $@;
586bfa78
AD
2827 };
2828 if (my $err = $@) {
2829
2830 foreach my $volid (@$newvollist) {
2831 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2832 warn $@ if $@;
2833 }
2834 die "storage migration failed: $err";
2835 }
70d45e33
DM
2836
2837 if ($param->{delete}) {
a3d0bafb
FG
2838 eval {
2839 PVE::Storage::deactivate_volumes($storecfg, [$old_volid]);
2840 PVE::Storage::vdisk_free($storecfg, $old_volid);
2841 };
2842 warn $@ if $@;
70d45e33 2843 }
586bfa78
AD
2844 };
2845
2846 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2847 };
e2cd75fa 2848
ffda963f 2849 return PVE::QemuConfig->lock_config($vmid, $updatefn);
586bfa78
AD
2850 }});
2851
3ea94c60 2852__PACKAGE__->register_method({
afdb31d5 2853 name => 'migrate_vm',
3ea94c60
DM
2854 path => '{vmid}/migrate',
2855 method => 'POST',
2856 protected => 1,
2857 proxyto => 'node',
2858 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2859 permissions => {
2860 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2861 },
3ea94c60
DM
2862 parameters => {
2863 additionalProperties => 0,
2864 properties => {
2865 node => get_standard_option('pve-node'),
335af808
DM
2866 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2867 target => get_standard_option('pve-node', {
2868 description => "Target node.",
2869 completion => \&PVE::Cluster::complete_migration_target,
2870 }),
3ea94c60
DM
2871 online => {
2872 type => 'boolean',
2873 description => "Use online/live migration.",
2874 optional => 1,
2875 },
2876 force => {
2877 type => 'boolean',
2878 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2879 optional => 1,
2880 },
2de2d6f7
TL
2881 migration_type => {
2882 type => 'string',
2883 enum => ['secure', 'insecure'],
c07a9e3d 2884 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
2885 optional => 1,
2886 },
2887 migration_network => {
c07a9e3d 2888 type => 'string', format => 'CIDR',
2de2d6f7
TL
2889 description => "CIDR of the (sub) network that is used for migration.",
2890 optional => 1,
2891 },
56af7146
AD
2892 "with-local-disks" => {
2893 type => 'boolean',
2894 description => "Enable live storage migration for local disk",
b74cad8a 2895 optional => 1,
56af7146
AD
2896 },
2897 targetstorage => get_standard_option('pve-storage-id', {
2898 description => "Default target storage.",
2899 optional => 1,
2900 completion => \&PVE::QemuServer::complete_storage,
2901 }),
3ea94c60
DM
2902 },
2903 },
afdb31d5 2904 returns => {
3ea94c60
DM
2905 type => 'string',
2906 description => "the task ID.",
2907 },
2908 code => sub {
2909 my ($param) = @_;
2910
2911 my $rpcenv = PVE::RPCEnvironment::get();
2912
a0d1b1a2 2913 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2914
2915 my $target = extract_param($param, 'target');
2916
2917 my $localnode = PVE::INotify::nodename();
2918 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2919
2920 PVE::Cluster::check_cfs_quorum();
2921
2922 PVE::Cluster::check_node_exists($target);
2923
2924 my $targetip = PVE::Cluster::remote_node_ip($target);
2925
2926 my $vmid = extract_param($param, 'vmid');
2927
bd2d5fe6 2928 raise_param_exc({ targetstorage => "Live storage migration can only be done online." })
b74cad8a
AD
2929 if !$param->{online} && $param->{targetstorage};
2930
afdb31d5 2931 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2932 if $param->{force} && $authuser ne 'root@pam';
3ea94c60 2933
2de2d6f7
TL
2934 raise_param_exc({ migration_type => "Only root may use this option." })
2935 if $param->{migration_type} && $authuser ne 'root@pam';
2936
2937 # allow root only until better network permissions are available
2938 raise_param_exc({ migration_network => "Only root may use this option." })
2939 if $param->{migration_network} && $authuser ne 'root@pam';
2940
3ea94c60 2941 # test if VM exists
ffda963f 2942 my $conf = PVE::QemuConfig->load_config($vmid);
3ea94c60
DM
2943
2944 # try to detect errors early
a5ed42d3 2945
ffda963f 2946 PVE::QemuConfig->check_lock($conf);
a5ed42d3 2947
3ea94c60 2948 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2949 die "cant migrate running VM without --online\n"
3ea94c60
DM
2950 if !$param->{online};
2951 }
2952
47152e2e 2953 my $storecfg = PVE::Storage::config();
d80ad67f
AD
2954
2955 if( $param->{targetstorage}) {
2956 PVE::Storage::storage_check_node($storecfg, $param->{targetstorage}, $target);
2957 } else {
2958 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
2959 }
47152e2e 2960
2003f0f8 2961 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2962
88fc87b4
DM
2963 my $hacmd = sub {
2964 my $upid = shift;
3ea94c60 2965
c44291cd 2966 my $service = "vm:$vmid";
88fc87b4 2967
2003f0f8 2968 my $cmd = ['ha-manager', 'migrate', $service, $target];
88fc87b4 2969
02765844 2970 print "Requesting HA migration for VM $vmid to node $target\n";
88fc87b4
DM
2971
2972 PVE::Tools::run_command($cmd);
2973
2974 return;
2975 };
2976
2977 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2978
2979 } else {
2980
f53c6ad8 2981 my $realcmd = sub {
f53c6ad8
DM
2982 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2983 };
88fc87b4 2984
f53c6ad8
DM
2985 my $worker = sub {
2986 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
88fc87b4
DM
2987 };
2988
f53c6ad8 2989 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
88fc87b4 2990 }
3ea94c60 2991
3ea94c60 2992 }});
1e3baf05 2993
91c94f0a 2994__PACKAGE__->register_method({
afdb31d5
DM
2995 name => 'monitor',
2996 path => '{vmid}/monitor',
91c94f0a
DM
2997 method => 'POST',
2998 protected => 1,
2999 proxyto => 'node',
3000 description => "Execute Qemu monitor commands.",
a0d1b1a2 3001 permissions => {
a8f2f427 3002 description => "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
c07a9e3d 3003 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
a0d1b1a2 3004 },
91c94f0a
DM
3005 parameters => {
3006 additionalProperties => 0,
3007 properties => {
3008 node => get_standard_option('pve-node'),
3009 vmid => get_standard_option('pve-vmid'),
3010 command => {
3011 type => 'string',
3012 description => "The monitor command.",
3013 }
3014 },
3015 },
3016 returns => { type => 'string'},
3017 code => sub {
3018 my ($param) = @_;
3019
a8f2f427
FG
3020 my $rpcenv = PVE::RPCEnvironment::get();
3021 my $authuser = $rpcenv->get_user();
3022
3023 my $is_ro = sub {
3024 my $command = shift;
3025 return $command =~ m/^\s*info(\s+|$)/
3026 || $command =~ m/^\s*help\s*$/;
3027 };
3028
3029 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3030 if !&$is_ro($param->{command});
3031
91c94f0a
DM
3032 my $vmid = $param->{vmid};
3033
ffda963f 3034 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
91c94f0a
DM
3035
3036 my $res = '';
3037 eval {
7b7c6d1b 3038 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
3039 };
3040 $res = "ERROR: $@" if $@;
3041
3042 return $res;
3043 }});
3044
a5d5341c 3045my $guest_agent_commands = [
249d8fed
DM
3046 'ping',
3047 'get-time',
3048 'info',
3049 'fsfreeze-status',
3050 'fsfreeze-freeze',
3051 'fsfreeze-thaw',
3052 'fstrim',
3053 'network-get-interfaces',
3054 'get-vcpus',
3055 'get-fsinfo',
3056 'get-memory-blocks',
3057 'get-memory-block-info',
3058 'suspend-hybrid',
3059 'suspend-ram',
3060 'suspend-disk',
3061 'shutdown',
a5d5341c
DM
3062 ];
3063
d1a47427
WL
3064__PACKAGE__->register_method({
3065 name => 'agent',
3066 path => '{vmid}/agent',
3067 method => 'POST',
3068 protected => 1,
3069 proxyto => 'node',
3070 description => "Execute Qemu Guest Agent commands.",
3071 permissions => {
3072 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3073 },
3074 parameters => {
3075 additionalProperties => 0,
3076 properties => {
3077 node => get_standard_option('pve-node'),
f38c5e27
DM
3078 vmid => get_standard_option('pve-vmid', {
3079 completion => \&PVE::QemuServer::complete_vmid_running }),
d1a47427
WL
3080 command => {
3081 type => 'string',
3082 description => "The QGA command.",
a5d5341c 3083 enum => $guest_agent_commands,
c07a9e3d 3084 },
d1a47427
WL
3085 },
3086 },
57bdd459
DM
3087 returns => {
3088 type => 'object',
3089 description => "Returns an object with a single `result` property. The type of that
3090property depends on the executed command.",
3091 },
d1a47427
WL
3092 code => sub {
3093 my ($param) = @_;
3094
3095 my $vmid = $param->{vmid};
3096
3097 my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
3098
d1a47427
WL
3099 die "No Qemu Guest Agent\n" if !defined($conf->{agent});
3100 die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
3101
249d8fed
DM
3102 my $cmd = $param->{command};
3103
3104 my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
d1a47427 3105
57bdd459 3106 return { result => $res };
d1a47427
WL
3107 }});
3108
0d02881c
AD
3109__PACKAGE__->register_method({
3110 name => 'resize_vm',
614e3941 3111 path => '{vmid}/resize',
0d02881c
AD
3112 method => 'PUT',
3113 protected => 1,
3114 proxyto => 'node',
2f48a4f5 3115 description => "Extend volume size.",
0d02881c 3116 permissions => {
3b2773f6 3117 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
3118 },
3119 parameters => {
3120 additionalProperties => 0,
2f48a4f5
DM
3121 properties => {
3122 node => get_standard_option('pve-node'),
335af808 3123 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
2f48a4f5
DM
3124 skiplock => get_standard_option('skiplock'),
3125 disk => {
3126 type => 'string',
3127 description => "The disk you want to resize.",
74479ee9 3128 enum => [PVE::QemuServer::valid_drive_names()],
2f48a4f5
DM
3129 },
3130 size => {
3131 type => 'string',
f91b2e45 3132 pattern => '\+?\d+(\.\d+)?[KMGT]?',
e248477e 3133 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
3134 },
3135 digest => {
3136 type => 'string',
3137 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3138 maxLength => 40,
3139 optional => 1,
3140 },
3141 },
0d02881c
AD
3142 },
3143 returns => { type => 'null'},
3144 code => sub {
3145 my ($param) = @_;
3146
3147 my $rpcenv = PVE::RPCEnvironment::get();
3148
3149 my $authuser = $rpcenv->get_user();
3150
3151 my $node = extract_param($param, 'node');
3152
3153 my $vmid = extract_param($param, 'vmid');
3154
3155 my $digest = extract_param($param, 'digest');
3156
2f48a4f5 3157 my $disk = extract_param($param, 'disk');
75466c4f 3158
2f48a4f5 3159 my $sizestr = extract_param($param, 'size');
0d02881c 3160
f91b2e45 3161 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
3162 raise_param_exc({ skiplock => "Only root may use this option." })
3163 if $skiplock && $authuser ne 'root@pam';
3164
0d02881c
AD
3165 my $storecfg = PVE::Storage::config();
3166
0d02881c
AD
3167 my $updatefn = sub {
3168
ffda963f 3169 my $conf = PVE::QemuConfig->load_config($vmid);
0d02881c
AD
3170
3171 die "checksum missmatch (file change by other user?)\n"
3172 if $digest && $digest ne $conf->{digest};
ffda963f 3173 PVE::QemuConfig->check_lock($conf) if !$skiplock;
0d02881c 3174
f91b2e45
DM
3175 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3176
3177 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
3178
d662790a
WL
3179 my (undef, undef, undef, undef, undef, undef, $format) =
3180 PVE::Storage::parse_volname($storecfg, $drive->{file});
3181
3182 die "can't resize volume: $disk if snapshot exists\n"
3183 if %{$conf->{snapshots}} && $format eq 'qcow2';
3184
f91b2e45
DM
3185 my $volid = $drive->{file};
3186
3187 die "disk '$disk' has no associated volume\n" if !$volid;
3188
3189 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
3190
3191 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
3192
3193 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3194
b572a606 3195 PVE::Storage::activate_volumes($storecfg, [$volid]);
f91b2e45
DM
3196 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
3197
3198 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3199 my ($ext, $newsize, $unit) = ($1, $2, $4);
3200 if ($unit) {
3201 if ($unit eq 'K') {
3202 $newsize = $newsize * 1024;
3203 } elsif ($unit eq 'M') {
3204 $newsize = $newsize * 1024 * 1024;
3205 } elsif ($unit eq 'G') {
3206 $newsize = $newsize * 1024 * 1024 * 1024;
3207 } elsif ($unit eq 'T') {
3208 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3209 }
3210 }
3211 $newsize += $size if $ext;
3212 $newsize = int($newsize);
3213
9a478b17 3214 die "shrinking disks is not supported\n" if $newsize < $size;
f91b2e45
DM
3215
3216 return if $size == $newsize;
3217
2f48a4f5 3218 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 3219
f91b2e45 3220 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 3221
f91b2e45
DM
3222 $drive->{size} = $newsize;
3223 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
3224
ffda963f 3225 PVE::QemuConfig->write_config($vmid, $conf);
f91b2e45 3226 };
0d02881c 3227
ffda963f 3228 PVE::QemuConfig->lock_config($vmid, $updatefn);
0d02881c
AD
3229 return undef;
3230 }});
3231
9dbd1ee4 3232__PACKAGE__->register_method({
7e7d7b61 3233 name => 'snapshot_list',
9dbd1ee4 3234 path => '{vmid}/snapshot',
7e7d7b61
DM
3235 method => 'GET',
3236 description => "List all snapshots.",
3237 permissions => {
3238 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3239 },
3240 proxyto => 'node',
3241 protected => 1, # qemu pid files are only readable by root
3242 parameters => {
3243 additionalProperties => 0,
3244 properties => {
e261de40 3245 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
7e7d7b61
DM
3246 node => get_standard_option('pve-node'),
3247 },
3248 },
3249 returns => {
3250 type => 'array',
3251 items => {
3252 type => "object",
3253 properties => {},
3254 },
3255 links => [ { rel => 'child', href => "{name}" } ],
3256 },
3257 code => sub {
3258 my ($param) = @_;
3259
6aa4651b
DM
3260 my $vmid = $param->{vmid};
3261
ffda963f 3262 my $conf = PVE::QemuConfig->load_config($vmid);
7e7d7b61
DM
3263 my $snaphash = $conf->{snapshots} || {};
3264
3265 my $res = [];
3266
3267 foreach my $name (keys %$snaphash) {
0ea6bc69 3268 my $d = $snaphash->{$name};
75466c4f
DM
3269 my $item = {
3270 name => $name,
3271 snaptime => $d->{snaptime} || 0,
6aa4651b 3272 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
3273 description => $d->{description} || '',
3274 };
0ea6bc69 3275 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 3276 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
3277 push @$res, $item;
3278 }
3279
6aa4651b
DM
3280 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
3281 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
3282 $current->{parent} = $conf->{parent} if $conf->{parent};
3283
3284 push @$res, $current;
7e7d7b61
DM
3285
3286 return $res;
3287 }});
3288
3289__PACKAGE__->register_method({
3290 name => 'snapshot',
3291 path => '{vmid}/snapshot',
3292 method => 'POST',
9dbd1ee4
AD
3293 protected => 1,
3294 proxyto => 'node',
3295 description => "Snapshot a VM.",
3296 permissions => {
f1baf1df 3297 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
3298 },
3299 parameters => {
3300 additionalProperties => 0,
3301 properties => {
3302 node => get_standard_option('pve-node'),
335af808 3303 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3304 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
3305 vmstate => {
3306 optional => 1,
3307 type => 'boolean',
3308 description => "Save the vmstate",
3309 },
782f4f75
DM
3310 description => {
3311 optional => 1,
3312 type => 'string',
3313 description => "A textual description or comment.",
3314 },
9dbd1ee4
AD
3315 },
3316 },
7e7d7b61
DM
3317 returns => {
3318 type => 'string',
3319 description => "the task ID.",
3320 },
9dbd1ee4
AD
3321 code => sub {
3322 my ($param) = @_;
3323
3324 my $rpcenv = PVE::RPCEnvironment::get();
3325
3326 my $authuser = $rpcenv->get_user();
3327
3328 my $node = extract_param($param, 'node');
3329
3330 my $vmid = extract_param($param, 'vmid');
3331
9dbd1ee4
AD
3332 my $snapname = extract_param($param, 'snapname');
3333
d1914468
DM
3334 die "unable to use snapshot name 'current' (reserved name)\n"
3335 if $snapname eq 'current';
3336
7e7d7b61 3337 my $realcmd = sub {
22c377f0 3338 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
b2c9558d 3339 PVE::QemuConfig->snapshot_create($vmid, $snapname, $param->{vmstate},
af9110dd 3340 $param->{description});
7e7d7b61
DM
3341 };
3342
3343 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3344 }});
3345
154ccdcd
DM
3346__PACKAGE__->register_method({
3347 name => 'snapshot_cmd_idx',
3348 path => '{vmid}/snapshot/{snapname}',
3349 description => '',
3350 method => 'GET',
3351 permissions => {
3352 user => 'all',
3353 },
3354 parameters => {
3355 additionalProperties => 0,
3356 properties => {
3357 vmid => get_standard_option('pve-vmid'),
3358 node => get_standard_option('pve-node'),
8abd398b 3359 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
3360 },
3361 },
3362 returns => {
3363 type => 'array',
3364 items => {
3365 type => "object",
3366 properties => {},
3367 },
3368 links => [ { rel => 'child', href => "{cmd}" } ],
3369 },
3370 code => sub {
3371 my ($param) = @_;
3372
3373 my $res = [];
3374
3375 push @$res, { cmd => 'rollback' };
d788cea6 3376 push @$res, { cmd => 'config' };
154ccdcd
DM
3377
3378 return $res;
3379 }});
3380
d788cea6
DM
3381__PACKAGE__->register_method({
3382 name => 'update_snapshot_config',
3383 path => '{vmid}/snapshot/{snapname}/config',
3384 method => 'PUT',
3385 protected => 1,
3386 proxyto => 'node',
3387 description => "Update snapshot metadata.",
3388 permissions => {
3389 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3390 },
3391 parameters => {
3392 additionalProperties => 0,
3393 properties => {
3394 node => get_standard_option('pve-node'),
3395 vmid => get_standard_option('pve-vmid'),
3396 snapname => get_standard_option('pve-snapshot-name'),
3397 description => {
3398 optional => 1,
3399 type => 'string',
3400 description => "A textual description or comment.",
3401 },
3402 },
3403 },
3404 returns => { type => 'null' },
3405 code => sub {
3406 my ($param) = @_;
3407
3408 my $rpcenv = PVE::RPCEnvironment::get();
3409
3410 my $authuser = $rpcenv->get_user();
3411
3412 my $vmid = extract_param($param, 'vmid');
3413
3414 my $snapname = extract_param($param, 'snapname');
3415
3416 return undef if !defined($param->{description});
3417
3418 my $updatefn = sub {
3419
ffda963f 3420 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6 3421
ffda963f 3422 PVE::QemuConfig->check_lock($conf);
d788cea6
DM
3423
3424 my $snap = $conf->{snapshots}->{$snapname};
3425
75466c4f
DM
3426 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3427
d788cea6
DM
3428 $snap->{description} = $param->{description} if defined($param->{description});
3429
ffda963f 3430 PVE::QemuConfig->write_config($vmid, $conf);
d788cea6
DM
3431 };
3432
ffda963f 3433 PVE::QemuConfig->lock_config($vmid, $updatefn);
d788cea6
DM
3434
3435 return undef;
3436 }});
3437
3438__PACKAGE__->register_method({
3439 name => 'get_snapshot_config',
3440 path => '{vmid}/snapshot/{snapname}/config',
3441 method => 'GET',
3442 proxyto => 'node',
3443 description => "Get snapshot configuration",
3444 permissions => {
c268337d 3445 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
d788cea6
DM
3446 },
3447 parameters => {
3448 additionalProperties => 0,
3449 properties => {
3450 node => get_standard_option('pve-node'),
3451 vmid => get_standard_option('pve-vmid'),
3452 snapname => get_standard_option('pve-snapshot-name'),
3453 },
3454 },
3455 returns => { type => "object" },
3456 code => sub {
3457 my ($param) = @_;
3458
3459 my $rpcenv = PVE::RPCEnvironment::get();
3460
3461 my $authuser = $rpcenv->get_user();
3462
3463 my $vmid = extract_param($param, 'vmid');
3464
3465 my $snapname = extract_param($param, 'snapname');
3466
ffda963f 3467 my $conf = PVE::QemuConfig->load_config($vmid);
d788cea6
DM
3468
3469 my $snap = $conf->{snapshots}->{$snapname};
3470
75466c4f
DM
3471 die "snapshot '$snapname' does not exist\n" if !defined($snap);
3472
d788cea6
DM
3473 return $snap;
3474 }});
3475
7e7d7b61
DM
3476__PACKAGE__->register_method({
3477 name => 'rollback',
154ccdcd 3478 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
3479 method => 'POST',
3480 protected => 1,
3481 proxyto => 'node',
3482 description => "Rollback VM state to specified snapshot.",
3483 permissions => {
c268337d 3484 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any => 1],
7e7d7b61
DM
3485 },
3486 parameters => {
3487 additionalProperties => 0,
3488 properties => {
3489 node => get_standard_option('pve-node'),
335af808 3490 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3491 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
3492 },
3493 },
3494 returns => {
3495 type => 'string',
3496 description => "the task ID.",
3497 },
3498 code => sub {
3499 my ($param) = @_;
3500
3501 my $rpcenv = PVE::RPCEnvironment::get();
3502
3503 my $authuser = $rpcenv->get_user();
3504
3505 my $node = extract_param($param, 'node');
3506
3507 my $vmid = extract_param($param, 'vmid');
3508
3509 my $snapname = extract_param($param, 'snapname');
3510
7e7d7b61 3511 my $realcmd = sub {
22c377f0 3512 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
b2c9558d 3513 PVE::QemuConfig->snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
3514 };
3515
c068c1c3
WL
3516 my $worker = sub {
3517 # hold migration lock, this makes sure that nobody create replication snapshots
3518 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
3519 };
3520
3521 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
7e7d7b61
DM
3522 }});
3523
3524__PACKAGE__->register_method({
3525 name => 'delsnapshot',
3526 path => '{vmid}/snapshot/{snapname}',
3527 method => 'DELETE',
3528 protected => 1,
3529 proxyto => 'node',
3530 description => "Delete a VM snapshot.",
3531 permissions => {
f1baf1df 3532 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
3533 },
3534 parameters => {
3535 additionalProperties => 0,
3536 properties => {
3537 node => get_standard_option('pve-node'),
335af808 3538 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
8abd398b 3539 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
3540 force => {
3541 optional => 1,
3542 type => 'boolean',
3543 description => "For removal from config file, even if removing disk snapshots fails.",
3544 },
7e7d7b61
DM
3545 },
3546 },
3547 returns => {
3548 type => 'string',
3549 description => "the task ID.",
3550 },
3551 code => sub {
3552 my ($param) = @_;
3553
3554 my $rpcenv = PVE::RPCEnvironment::get();
3555
3556 my $authuser = $rpcenv->get_user();
3557
3558 my $node = extract_param($param, 'node');
3559
3560 my $vmid = extract_param($param, 'vmid');
3561
3562 my $snapname = extract_param($param, 'snapname');
3563
7e7d7b61 3564 my $realcmd = sub {
22c377f0 3565 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
b2c9558d 3566 PVE::QemuConfig->snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3567 };
9dbd1ee4 3568
7b2257a8 3569 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3570 }});
3571
04a69bb4
AD
3572__PACKAGE__->register_method({
3573 name => 'template',
3574 path => '{vmid}/template',
3575 method => 'POST',
3576 protected => 1,
3577 proxyto => 'node',
3578 description => "Create a Template.",
b02691d8 3579 permissions => {
7af0a6c8
DM
3580 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3581 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3582 },
04a69bb4
AD
3583 parameters => {
3584 additionalProperties => 0,
3585 properties => {
3586 node => get_standard_option('pve-node'),
335af808 3587 vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid_stopped }),
04a69bb4
AD
3588 disk => {
3589 optional => 1,
3590 type => 'string',
3591 description => "If you want to convert only 1 disk to base image.",
74479ee9 3592 enum => [PVE::QemuServer::valid_drive_names()],
04a69bb4
AD
3593 },
3594
3595 },
3596 },
3597 returns => { type => 'null'},
3598 code => sub {
3599 my ($param) = @_;
3600
3601 my $rpcenv = PVE::RPCEnvironment::get();
3602
3603 my $authuser = $rpcenv->get_user();
3604
3605 my $node = extract_param($param, 'node');
3606
3607 my $vmid = extract_param($param, 'vmid');
3608
3609 my $disk = extract_param($param, 'disk');
3610
3611 my $updatefn = sub {
3612
ffda963f 3613 my $conf = PVE::QemuConfig->load_config($vmid);
04a69bb4 3614
ffda963f 3615 PVE::QemuConfig->check_lock($conf);
04a69bb4 3616
75466c4f 3617 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3618 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3619
75466c4f 3620 die "you can't convert a template to a template\n"
ffda963f 3621 if PVE::QemuConfig->is_template($conf) && !$disk;
0402a80b 3622
75466c4f 3623 die "you can't convert a VM to template if VM is running\n"
218cab9a 3624 if PVE::QemuServer::check_running($vmid);
35c5fdef 3625
04a69bb4
AD
3626 my $realcmd = sub {
3627 PVE::QemuServer::template_create($vmid, $conf, $disk);
3628 };
04a69bb4 3629
75e7e997 3630 $conf->{template} = 1;
ffda963f 3631 PVE::QemuConfig->write_config($vmid, $conf);
75e7e997
DM
3632
3633 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3634 };
3635
ffda963f 3636 PVE::QemuConfig->lock_config($vmid, $updatefn);
04a69bb4
AD
3637 return undef;
3638 }});
3639
1e3baf05 36401;