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