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