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