]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
bump version to 3.0-24
[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'),
fb6c7260
DM
1341 proxy => {
1342 description => "This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.host for the JS GUI).",
1343 type => 'string', format => 'dns-name',
1344 optional => 1,
1345 },
288eeea8
DM
1346 },
1347 },
1348 returns => {
fb6c7260 1349 description => "Returned values can be directly passed to the 'remote-viewer' application.",
288eeea8
DM
1350 additionalProperties => 1,
1351 properties => {
1352 type => { type => 'string' },
1353 password => { type => 'string' },
3309e65a 1354 proxy => { type => 'string' },
288eeea8 1355 host => { type => 'string' },
943340a6 1356 'tls-port' => { type => 'integer' },
288eeea8
DM
1357 },
1358 },
1359 code => sub {
1360 my ($param) = @_;
1361
1362 my $rpcenv = PVE::RPCEnvironment::get();
1363
1364 my $authuser = $rpcenv->get_user();
1365
1366 my $vmid = $param->{vmid};
1367 my $node = $param->{node};
fb6c7260 1368 my $proxy = $param->{proxy};
288eeea8 1369
3309e65a 1370 my ($ticket, $proxyticket) = PVE::AccessControl::assemble_spice_ticket($authuser, $vmid, $node);
5ecf258f 1371
288eeea8
DM
1372 my $timeout = 10;
1373
943340a6 1374 my $port = PVE::QemuServer::spice_port($vmid);
288eeea8
DM
1375 PVE::QemuServer::vm_mon_cmd($vmid, "set_password", protocol => 'spice', password => $ticket);
1376 PVE::QemuServer::vm_mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
1377
fb6c7260
DM
1378 if (!$proxy) {
1379 my $host = `hostname -f` || PVE::INotify::nodename();
1380 chomp $host;
1381 $proxy = $host;
1382 }
288eeea8 1383
451b2b81
DM
1384 # read x509 subject
1385 my $filename = "/etc/pve/local/pve-ssl.pem";
1386 my $bio = Net::SSLeay::BIO_new_file($filename, 'r');
1387 my $x509 = Net::SSLeay::PEM_read_bio_X509($bio);
1388 Net::SSLeay::BIO_free($bio);
1389 my $nameobj = Net::SSLeay::X509_get_subject_name($x509);
1390 my $subject = Net::SSLeay::X509_NAME_oneline($nameobj);
1391 Net::SSLeay::X509_free($x509);
1392
1393 # remote-viewer wants comma as seperator (not '/')
1394 $subject =~ s!^/!!;
1395 $subject =~ s!/(\w+=)!,$1!g;
943340a6
DM
1396
1397 my $cacert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192);
1398 $cacert =~ s/\n/\\n/g;
1399
288eeea8
DM
1400 return {
1401 type => 'spice',
943340a6
DM
1402 title => "VM $vmid",
1403 host => $proxyticket, # this break tls hostname verification, so we need to use 'host-subject'
fb6c7260 1404 proxy => "http://$proxy:3128",
943340a6
DM
1405 'tls-port' => $port,
1406 'host-subject' => $subject,
1407 ca => $cacert,
716a470c
DM
1408 password => $ticket,
1409 'delete-this-file' => 1,
288eeea8
DM
1410 };
1411 }});
1412
5fdbe4f0
DM
1413__PACKAGE__->register_method({
1414 name => 'vmcmdidx',
afdb31d5 1415 path => '{vmid}/status',
5fdbe4f0
DM
1416 method => 'GET',
1417 proxyto => 'node',
1418 description => "Directory index",
a0d1b1a2
DM
1419 permissions => {
1420 user => 'all',
1421 },
5fdbe4f0
DM
1422 parameters => {
1423 additionalProperties => 0,
1424 properties => {
1425 node => get_standard_option('pve-node'),
1426 vmid => get_standard_option('pve-vmid'),
1427 },
1428 },
1429 returns => {
1430 type => 'array',
1431 items => {
1432 type => "object",
1433 properties => {
1434 subdir => { type => 'string' },
1435 },
1436 },
1437 links => [ { rel => 'child', href => "{subdir}" } ],
1438 },
1439 code => sub {
1440 my ($param) = @_;
1441
1442 # test if VM exists
1443 my $conf = PVE::QemuServer::load_config($param->{vmid});
1444
1445 my $res = [
1446 { subdir => 'current' },
1447 { subdir => 'start' },
1448 { subdir => 'stop' },
1449 ];
afdb31d5 1450
5fdbe4f0
DM
1451 return $res;
1452 }});
1453
88fc87b4
DM
1454my $vm_is_ha_managed = sub {
1455 my ($vmid) = @_;
1456
1457 my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
1458 if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) {
1459 return 1;
75466c4f 1460 }
88fc87b4
DM
1461 return 0;
1462};
1463
1e3baf05 1464__PACKAGE__->register_method({
afdb31d5 1465 name => 'vm_status',
5fdbe4f0 1466 path => '{vmid}/status/current',
1e3baf05
DM
1467 method => 'GET',
1468 proxyto => 'node',
1469 protected => 1, # qemu pid files are only readable by root
1470 description => "Get virtual machine status.",
a0d1b1a2
DM
1471 permissions => {
1472 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1473 },
1e3baf05
DM
1474 parameters => {
1475 additionalProperties => 0,
1476 properties => {
1477 node => get_standard_option('pve-node'),
1478 vmid => get_standard_option('pve-vmid'),
1479 },
1480 },
1481 returns => { type => 'object' },
1482 code => sub {
1483 my ($param) = @_;
1484
1485 # test if VM exists
1486 my $conf = PVE::QemuServer::load_config($param->{vmid});
1487
03a33f30 1488 my $vmstatus = PVE::QemuServer::vmstatus($param->{vmid}, 1);
8610701a 1489 my $status = $vmstatus->{$param->{vmid}};
1e3baf05 1490
88fc87b4 1491 $status->{ha} = &$vm_is_ha_managed($param->{vmid});
8610701a 1492
46246f04
DM
1493 if ($conf->{vga} && ($conf->{vga} eq 'qxl')) {
1494 $status->{spice} = 1;
1495 }
1496
8610701a 1497 return $status;
1e3baf05
DM
1498 }});
1499
1500__PACKAGE__->register_method({
afdb31d5 1501 name => 'vm_start',
5fdbe4f0
DM
1502 path => '{vmid}/status/start',
1503 method => 'POST',
1e3baf05
DM
1504 protected => 1,
1505 proxyto => 'node',
5fdbe4f0 1506 description => "Start virtual machine.",
a0d1b1a2
DM
1507 permissions => {
1508 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1509 },
1e3baf05
DM
1510 parameters => {
1511 additionalProperties => 0,
1512 properties => {
1513 node => get_standard_option('pve-node'),
1514 vmid => get_standard_option('pve-vmid'),
3ea94c60
DM
1515 skiplock => get_standard_option('skiplock'),
1516 stateuri => get_standard_option('pve-qm-stateuri'),
7e8dcf2c 1517 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
952958bc 1518 machine => get_standard_option('pve-qm-machine'),
1e3baf05
DM
1519 },
1520 },
afdb31d5 1521 returns => {
5fdbe4f0
DM
1522 type => 'string',
1523 },
1e3baf05
DM
1524 code => sub {
1525 my ($param) = @_;
1526
1527 my $rpcenv = PVE::RPCEnvironment::get();
1528
a0d1b1a2 1529 my $authuser = $rpcenv->get_user();
1e3baf05
DM
1530
1531 my $node = extract_param($param, 'node');
1532
1e3baf05
DM
1533 my $vmid = extract_param($param, 'vmid');
1534
952958bc
DM
1535 my $machine = extract_param($param, 'machine');
1536
3ea94c60 1537 my $stateuri = extract_param($param, 'stateuri');
afdb31d5 1538 raise_param_exc({ stateuri => "Only root may use this option." })
a0d1b1a2 1539 if $stateuri && $authuser ne 'root@pam';
3ea94c60 1540
1e3baf05 1541 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1542 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1543 if $skiplock && $authuser ne 'root@pam';
1e3baf05 1544
7e8dcf2c
AD
1545 my $migratedfrom = extract_param($param, 'migratedfrom');
1546 raise_param_exc({ migratedfrom => "Only root may use this option." })
1547 if $migratedfrom && $authuser ne 'root@pam';
1548
afdb31d5 1549 my $storecfg = PVE::Storage::config();
5fdbe4f0 1550
cce37749
DM
1551 if (&$vm_is_ha_managed($vmid) && !$stateuri &&
1552 $rpcenv->{type} ne 'ha') {
5fdbe4f0 1553
88fc87b4
DM
1554 my $hacmd = sub {
1555 my $upid = shift;
5fdbe4f0 1556
88fc87b4 1557 my $service = "pvevm:$vmid";
5fdbe4f0 1558
88fc87b4
DM
1559 my $cmd = ['clusvcadm', '-e', $service, '-m', $node];
1560
1561 print "Executing HA start for VM $vmid\n";
1562
1563 PVE::Tools::run_command($cmd);
1564
1565 return;
1566 };
1567
1568 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
1569
1570 } else {
1571
1572 my $realcmd = sub {
1573 my $upid = shift;
1574
1575 syslog('info', "start VM $vmid: $upid\n");
1576
952958bc 1577 PVE::QemuServer::vm_start($storecfg, $vmid, $stateuri, $skiplock, $migratedfrom, undef, $machine);
88fc87b4
DM
1578
1579 return;
1580 };
5fdbe4f0 1581
88fc87b4
DM
1582 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
1583 }
5fdbe4f0
DM
1584 }});
1585
1586__PACKAGE__->register_method({
afdb31d5 1587 name => 'vm_stop',
5fdbe4f0
DM
1588 path => '{vmid}/status/stop',
1589 method => 'POST',
1590 protected => 1,
1591 proxyto => 'node',
1592 description => "Stop virtual machine.",
a0d1b1a2
DM
1593 permissions => {
1594 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1595 },
5fdbe4f0
DM
1596 parameters => {
1597 additionalProperties => 0,
1598 properties => {
1599 node => get_standard_option('pve-node'),
1600 vmid => get_standard_option('pve-vmid'),
1601 skiplock => get_standard_option('skiplock'),
af30308f 1602 migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
c6bb9502
DM
1603 timeout => {
1604 description => "Wait maximal timeout seconds.",
1605 type => 'integer',
1606 minimum => 0,
1607 optional => 1,
254575e9
DM
1608 },
1609 keepActive => {
1610 description => "Do not decativate storage volumes.",
1611 type => 'boolean',
1612 optional => 1,
1613 default => 0,
c6bb9502 1614 }
5fdbe4f0
DM
1615 },
1616 },
afdb31d5 1617 returns => {
5fdbe4f0
DM
1618 type => 'string',
1619 },
1620 code => sub {
1621 my ($param) = @_;
1622
1623 my $rpcenv = PVE::RPCEnvironment::get();
1624
a0d1b1a2 1625 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1626
1627 my $node = extract_param($param, 'node');
1628
1629 my $vmid = extract_param($param, 'vmid');
1630
1631 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1632 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1633 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1634
254575e9 1635 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1636 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1637 if $keepActive && $authuser ne 'root@pam';
254575e9 1638
af30308f
DM
1639 my $migratedfrom = extract_param($param, 'migratedfrom');
1640 raise_param_exc({ migratedfrom => "Only root may use this option." })
1641 if $migratedfrom && $authuser ne 'root@pam';
1642
1643
ff1a2432
DM
1644 my $storecfg = PVE::Storage::config();
1645
3be30d63 1646 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
5fdbe4f0 1647
88fc87b4
DM
1648 my $hacmd = sub {
1649 my $upid = shift;
5fdbe4f0 1650
88fc87b4 1651 my $service = "pvevm:$vmid";
c6bb9502 1652
88fc87b4
DM
1653 my $cmd = ['clusvcadm', '-d', $service];
1654
1655 print "Executing HA stop for VM $vmid\n";
1656
1657 PVE::Tools::run_command($cmd);
5fdbe4f0 1658
88fc87b4
DM
1659 return;
1660 };
1661
1662 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1663
1664 } else {
1665 my $realcmd = sub {
1666 my $upid = shift;
1667
1668 syslog('info', "stop VM $vmid: $upid\n");
1669
1670 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0,
af30308f 1671 $param->{timeout}, 0, 1, $keepActive, $migratedfrom);
88fc87b4
DM
1672
1673 return;
1674 };
1675
1676 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
1677 }
5fdbe4f0
DM
1678 }});
1679
1680__PACKAGE__->register_method({
afdb31d5 1681 name => 'vm_reset',
5fdbe4f0
DM
1682 path => '{vmid}/status/reset',
1683 method => 'POST',
1684 protected => 1,
1685 proxyto => 'node',
1686 description => "Reset virtual machine.",
a0d1b1a2
DM
1687 permissions => {
1688 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1689 },
5fdbe4f0
DM
1690 parameters => {
1691 additionalProperties => 0,
1692 properties => {
1693 node => get_standard_option('pve-node'),
1694 vmid => get_standard_option('pve-vmid'),
1695 skiplock => get_standard_option('skiplock'),
1696 },
1697 },
afdb31d5 1698 returns => {
5fdbe4f0
DM
1699 type => 'string',
1700 },
1701 code => sub {
1702 my ($param) = @_;
1703
1704 my $rpcenv = PVE::RPCEnvironment::get();
1705
a0d1b1a2 1706 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1707
1708 my $node = extract_param($param, 'node');
1709
1710 my $vmid = extract_param($param, 'vmid');
1711
1712 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1713 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1714 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1715
ff1a2432
DM
1716 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1717
5fdbe4f0
DM
1718 my $realcmd = sub {
1719 my $upid = shift;
1720
1e3baf05 1721 PVE::QemuServer::vm_reset($vmid, $skiplock);
5fdbe4f0
DM
1722
1723 return;
1724 };
1725
a0d1b1a2 1726 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1727 }});
1728
1729__PACKAGE__->register_method({
afdb31d5 1730 name => 'vm_shutdown',
5fdbe4f0
DM
1731 path => '{vmid}/status/shutdown',
1732 method => 'POST',
1733 protected => 1,
1734 proxyto => 'node',
1735 description => "Shutdown virtual machine.",
a0d1b1a2
DM
1736 permissions => {
1737 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1738 },
5fdbe4f0
DM
1739 parameters => {
1740 additionalProperties => 0,
1741 properties => {
1742 node => get_standard_option('pve-node'),
1743 vmid => get_standard_option('pve-vmid'),
1744 skiplock => get_standard_option('skiplock'),
c6bb9502
DM
1745 timeout => {
1746 description => "Wait maximal timeout seconds.",
1747 type => 'integer',
1748 minimum => 0,
1749 optional => 1,
9269013a
DM
1750 },
1751 forceStop => {
1752 description => "Make sure the VM stops.",
1753 type => 'boolean',
1754 optional => 1,
1755 default => 0,
254575e9
DM
1756 },
1757 keepActive => {
1758 description => "Do not decativate storage volumes.",
1759 type => 'boolean',
1760 optional => 1,
1761 default => 0,
c6bb9502 1762 }
5fdbe4f0
DM
1763 },
1764 },
afdb31d5 1765 returns => {
5fdbe4f0
DM
1766 type => 'string',
1767 },
1768 code => sub {
1769 my ($param) = @_;
1770
1771 my $rpcenv = PVE::RPCEnvironment::get();
1772
a0d1b1a2 1773 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1774
1775 my $node = extract_param($param, 'node');
1776
1777 my $vmid = extract_param($param, 'vmid');
1778
1779 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1780 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1781 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1782
254575e9 1783 my $keepActive = extract_param($param, 'keepActive');
afdb31d5 1784 raise_param_exc({ keepActive => "Only root may use this option." })
a0d1b1a2 1785 if $keepActive && $authuser ne 'root@pam';
254575e9 1786
02d07cf5
DM
1787 my $storecfg = PVE::Storage::config();
1788
5fdbe4f0
DM
1789 my $realcmd = sub {
1790 my $upid = shift;
1791
1792 syslog('info', "shutdown VM $vmid: $upid\n");
1793
afdb31d5 1794 PVE::QemuServer::vm_stop($storecfg, $vmid, $skiplock, 0, $param->{timeout},
254575e9 1795 1, $param->{forceStop}, $keepActive);
c6bb9502 1796
5fdbe4f0
DM
1797 return;
1798 };
1799
a0d1b1a2 1800 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1801 }});
1802
1803__PACKAGE__->register_method({
afdb31d5 1804 name => 'vm_suspend',
5fdbe4f0
DM
1805 path => '{vmid}/status/suspend',
1806 method => 'POST',
1807 protected => 1,
1808 proxyto => 'node',
1809 description => "Suspend virtual machine.",
a0d1b1a2
DM
1810 permissions => {
1811 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1812 },
5fdbe4f0
DM
1813 parameters => {
1814 additionalProperties => 0,
1815 properties => {
1816 node => get_standard_option('pve-node'),
1817 vmid => get_standard_option('pve-vmid'),
1818 skiplock => get_standard_option('skiplock'),
1819 },
1820 },
afdb31d5 1821 returns => {
5fdbe4f0
DM
1822 type => 'string',
1823 },
1824 code => sub {
1825 my ($param) = @_;
1826
1827 my $rpcenv = PVE::RPCEnvironment::get();
1828
a0d1b1a2 1829 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1830
1831 my $node = extract_param($param, 'node');
1832
1833 my $vmid = extract_param($param, 'vmid');
1834
1835 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1836 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1837 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1838
ff1a2432
DM
1839 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
1840
5fdbe4f0
DM
1841 my $realcmd = sub {
1842 my $upid = shift;
1843
1844 syslog('info', "suspend VM $vmid: $upid\n");
1845
1e3baf05 1846 PVE::QemuServer::vm_suspend($vmid, $skiplock);
5fdbe4f0
DM
1847
1848 return;
1849 };
1850
a0d1b1a2 1851 return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1852 }});
1853
1854__PACKAGE__->register_method({
afdb31d5 1855 name => 'vm_resume',
5fdbe4f0
DM
1856 path => '{vmid}/status/resume',
1857 method => 'POST',
1858 protected => 1,
1859 proxyto => 'node',
1860 description => "Resume virtual machine.",
a0d1b1a2
DM
1861 permissions => {
1862 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1863 },
5fdbe4f0
DM
1864 parameters => {
1865 additionalProperties => 0,
1866 properties => {
1867 node => get_standard_option('pve-node'),
1868 vmid => get_standard_option('pve-vmid'),
1869 skiplock => get_standard_option('skiplock'),
1870 },
1871 },
afdb31d5 1872 returns => {
5fdbe4f0
DM
1873 type => 'string',
1874 },
1875 code => sub {
1876 my ($param) = @_;
1877
1878 my $rpcenv = PVE::RPCEnvironment::get();
1879
a0d1b1a2 1880 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1881
1882 my $node = extract_param($param, 'node');
1883
1884 my $vmid = extract_param($param, 'vmid');
1885
1886 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1887 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1888 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0 1889
b7eeab21 1890 die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
ff1a2432 1891
5fdbe4f0
DM
1892 my $realcmd = sub {
1893 my $upid = shift;
1894
1895 syslog('info', "resume VM $vmid: $upid\n");
1896
1e3baf05 1897 PVE::QemuServer::vm_resume($vmid, $skiplock);
1e3baf05 1898
5fdbe4f0
DM
1899 return;
1900 };
1901
a0d1b1a2 1902 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
5fdbe4f0
DM
1903 }});
1904
1905__PACKAGE__->register_method({
afdb31d5 1906 name => 'vm_sendkey',
5fdbe4f0
DM
1907 path => '{vmid}/sendkey',
1908 method => 'PUT',
1909 protected => 1,
1910 proxyto => 'node',
1911 description => "Send key event to virtual machine.",
a0d1b1a2
DM
1912 permissions => {
1913 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1914 },
5fdbe4f0
DM
1915 parameters => {
1916 additionalProperties => 0,
1917 properties => {
1918 node => get_standard_option('pve-node'),
1919 vmid => get_standard_option('pve-vmid'),
1920 skiplock => get_standard_option('skiplock'),
1921 key => {
1922 description => "The key (qemu monitor encoding).",
1923 type => 'string'
1924 }
1925 },
1926 },
1927 returns => { type => 'null'},
1928 code => sub {
1929 my ($param) = @_;
1930
1931 my $rpcenv = PVE::RPCEnvironment::get();
1932
a0d1b1a2 1933 my $authuser = $rpcenv->get_user();
5fdbe4f0
DM
1934
1935 my $node = extract_param($param, 'node');
1936
1937 my $vmid = extract_param($param, 'vmid');
1938
1939 my $skiplock = extract_param($param, 'skiplock');
afdb31d5 1940 raise_param_exc({ skiplock => "Only root may use this option." })
a0d1b1a2 1941 if $skiplock && $authuser ne 'root@pam';
5fdbe4f0
DM
1942
1943 PVE::QemuServer::vm_sendkey($vmid, $skiplock, $param->{key});
1944
1945 return;
1e3baf05
DM
1946 }});
1947
1ac0d2ee
AD
1948__PACKAGE__->register_method({
1949 name => 'vm_feature',
1950 path => '{vmid}/feature',
1951 method => 'GET',
1952 proxyto => 'node',
75466c4f 1953 protected => 1,
1ac0d2ee
AD
1954 description => "Check if feature for virtual machine is available.",
1955 permissions => {
1956 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1957 },
1958 parameters => {
1959 additionalProperties => 0,
1960 properties => {
1961 node => get_standard_option('pve-node'),
1962 vmid => get_standard_option('pve-vmid'),
1963 feature => {
1964 description => "Feature to check.",
1965 type => 'string',
7758ce86 1966 enum => [ 'snapshot', 'clone', 'copy' ],
1ac0d2ee
AD
1967 },
1968 snapname => get_standard_option('pve-snapshot-name', {
1969 optional => 1,
1970 }),
1971 },
1ac0d2ee
AD
1972 },
1973 returns => {
719893a9
DM
1974 type => "object",
1975 properties => {
1976 hasFeature => { type => 'boolean' },
7043d946 1977 nodes => {
719893a9
DM
1978 type => 'array',
1979 items => { type => 'string' },
1980 }
1981 },
1ac0d2ee
AD
1982 },
1983 code => sub {
1984 my ($param) = @_;
1985
1986 my $node = extract_param($param, 'node');
1987
1988 my $vmid = extract_param($param, 'vmid');
1989
1990 my $snapname = extract_param($param, 'snapname');
1991
1992 my $feature = extract_param($param, 'feature');
1993
1994 my $running = PVE::QemuServer::check_running($vmid);
1995
1996 my $conf = PVE::QemuServer::load_config($vmid);
1997
1998 if($snapname){
1999 my $snap = $conf->{snapshots}->{$snapname};
2000 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2001 $conf = $snap;
2002 }
2003 my $storecfg = PVE::Storage::config();
2004
719893a9
DM
2005 my $nodelist = PVE::QemuServer::shared_nodes($conf, $storecfg);
2006 my $hasFeature = PVE::QemuServer::has_feature($feature, $conf, $storecfg, $snapname, $running);
7043d946 2007
719893a9
DM
2008 return {
2009 hasFeature => $hasFeature,
2010 nodes => [ keys %$nodelist ],
7043d946 2011 };
1ac0d2ee
AD
2012 }});
2013
6116f729 2014__PACKAGE__->register_method({
9418baad
DM
2015 name => 'clone_vm',
2016 path => '{vmid}/clone',
6116f729
DM
2017 method => 'POST',
2018 protected => 1,
2019 proxyto => 'node',
37329185 2020 description => "Create a copy of virtual machine/template.",
6116f729 2021 permissions => {
9418baad 2022 description => "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
6116f729
DM
2023 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2024 "'Datastore.AllocateSpace' on any used storage.",
75466c4f
DM
2025 check =>
2026 [ 'and',
9418baad 2027 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
75466c4f 2028 [ 'or',
6116f729
DM
2029 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2030 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
2031 ],
2032 ]
2033 },
2034 parameters => {
2035 additionalProperties => 0,
2036 properties => {
6116f729
DM
2037 node => get_standard_option('pve-node'),
2038 vmid => get_standard_option('pve-vmid'),
9418baad 2039 newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }),
a60ab1a6
DM
2040 name => {
2041 optional => 1,
2042 type => 'string', format => 'dns-name',
2043 description => "Set a name for the new VM.",
2044 },
2045 description => {
2046 optional => 1,
2047 type => 'string',
2048 description => "Description for the new VM.",
2049 },
75466c4f 2050 pool => {
6116f729
DM
2051 optional => 1,
2052 type => 'string', format => 'pve-poolid',
2053 description => "Add the new VM to the specified pool.",
2054 },
9076d880
DM
2055 snapname => get_standard_option('pve-snapshot-name', {
2056 requires => 'full',
2057 optional => 1,
2058 }),
81f043eb 2059 storage => get_standard_option('pve-storage-id', {
9418baad 2060 description => "Target storage for full clone.",
4e4f83fe 2061 requires => 'full',
81f043eb
AD
2062 optional => 1,
2063 }),
55173c6b 2064 'format' => {
42a19c87
AD
2065 description => "Target format for file storage.",
2066 requires => 'full',
2067 type => 'string',
2068 optional => 1,
55173c6b 2069 enum => [ 'raw', 'qcow2', 'vmdk'],
42a19c87 2070 },
6116f729
DM
2071 full => {
2072 optional => 1,
55173c6b
DM
2073 type => 'boolean',
2074 description => "Create a full copy of all disk. This is always done when " .
9418baad 2075 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
6116f729
DM
2076 default => 0,
2077 },
75466c4f 2078 target => get_standard_option('pve-node', {
55173c6b
DM
2079 description => "Target node. Only allowed if the original VM is on shared storage.",
2080 optional => 1,
2081 }),
2082 },
6116f729
DM
2083 },
2084 returns => {
2085 type => 'string',
2086 },
2087 code => sub {
2088 my ($param) = @_;
2089
2090 my $rpcenv = PVE::RPCEnvironment::get();
2091
55173c6b 2092 my $authuser = $rpcenv->get_user();
6116f729
DM
2093
2094 my $node = extract_param($param, 'node');
2095
2096 my $vmid = extract_param($param, 'vmid');
2097
2098 my $newid = extract_param($param, 'newid');
2099
6116f729
DM
2100 my $pool = extract_param($param, 'pool');
2101
2102 if (defined($pool)) {
2103 $rpcenv->check_pool_exist($pool);
2104 }
2105
55173c6b 2106 my $snapname = extract_param($param, 'snapname');
9076d880 2107
81f043eb
AD
2108 my $storage = extract_param($param, 'storage');
2109
42a19c87
AD
2110 my $format = extract_param($param, 'format');
2111
55173c6b
DM
2112 my $target = extract_param($param, 'target');
2113
2114 my $localnode = PVE::INotify::nodename();
2115
751cc556 2116 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
55173c6b
DM
2117
2118 PVE::Cluster::check_node_exists($target) if $target;
2119
6116f729
DM
2120 my $storecfg = PVE::Storage::config();
2121
4a5a2590
DM
2122 if ($storage) {
2123 # check if storage is enabled on local node
2124 PVE::Storage::storage_check_enabled($storecfg, $storage);
2125 if ($target) {
2126 # check if storage is available on target node
2127 PVE::Storage::storage_check_node($storecfg, $storage, $target);
2128 # clone only works if target storage is shared
2129 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2130 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
2131 }
2132 }
2133
55173c6b 2134 PVE::Cluster::check_cfs_quorum();
6116f729 2135
4e4f83fe
DM
2136 my $running = PVE::QemuServer::check_running($vmid) || 0;
2137
4e4f83fe
DM
2138 # exclusive lock if VM is running - else shared lock is enough;
2139 my $shared_lock = $running ? 0 : 1;
2140
9418baad 2141 my $clonefn = sub {
6116f729 2142
829967a9
DM
2143 # do all tests after lock
2144 # we also try to do all tests before we fork the worker
2145
6116f729
DM
2146 my $conf = PVE::QemuServer::load_config($vmid);
2147
2148 PVE::QemuServer::check_lock($conf);
2149
4e4f83fe 2150 my $verify_running = PVE::QemuServer::check_running($vmid) || 0;
6116f729 2151
4e4f83fe 2152 die "unexpected state change\n" if $verify_running != $running;
6116f729 2153
75466c4f
DM
2154 die "snapshot '$snapname' does not exist\n"
2155 if $snapname && !defined( $conf->{snapshots}->{$snapname});
6116f729 2156
75466c4f 2157 my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
9076d880 2158
9418baad 2159 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
6116f729 2160
9418baad 2161 die "can't clone VM to node '$target' (VM uses local storage)\n" if $target && !$sharedvm;
75466c4f 2162
6116f729
DM
2163 my $conffile = PVE::QemuServer::config_file($newid);
2164
2165 die "unable to create VM $newid: config file already exists\n"
2166 if -f $conffile;
2167
9418baad 2168 my $newconf = { lock => 'clone' };
829967a9
DM
2169 my $drives = {};
2170 my $vollist = [];
2171
2172 foreach my $opt (keys %$oldconf) {
2173 my $value = $oldconf->{$opt};
2174
2175 # do not copy snapshot related info
2176 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
2177 $opt eq 'vmstate' || $opt eq 'snapstate';
2178
2179 # always change MAC! address
2180 if ($opt =~ m/^net(\d+)$/) {
2181 my $net = PVE::QemuServer::parse_net($value);
2182 $net->{macaddr} = PVE::Tools::random_ether_addr();
2183 $newconf->{$opt} = PVE::QemuServer::print_net($net);
2184 } elsif (my $drive = PVE::QemuServer::parse_drive($opt, $value)) {
2185 if (PVE::QemuServer::drive_is_cdrom($drive)) {
2186 $newconf->{$opt} = $value; # simply copy configuration
2187 } else {
dba198b0 2188 if ($param->{full} || !PVE::Storage::volume_is_base($storecfg, $drive->{file})) {
2dd53043 2189 die "Full clone feature is not available"
dba198b0 2190 if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
2dd53043 2191 $drive->{full} = 1;
dba198b0 2192 }
829967a9
DM
2193 $drives->{$opt} = $drive;
2194 push @$vollist, $drive->{file};
2195 }
2196 } else {
2197 # copy everything else
2198 $newconf->{$opt} = $value;
2199 }
2200 }
2201
2202 delete $newconf->{template};
2203
2204 if ($param->{name}) {
2205 $newconf->{name} = $param->{name};
2206 } else {
c55fee03
DM
2207 if ($oldconf->{name}) {
2208 $newconf->{name} = "Copy-of-$oldconf->{name}";
2209 } else {
2210 $newconf->{name} = "Copy-of-VM-$vmid";
2211 }
829967a9 2212 }
2dd53043 2213
829967a9
DM
2214 if ($param->{description}) {
2215 $newconf->{description} = $param->{description};
2216 }
2217
6116f729 2218 # create empty/temp config - this fails if VM already exists on other node
9418baad 2219 PVE::Tools::file_set_contents($conffile, "# qmclone temporary file\nlock: clone\n");
6116f729
DM
2220
2221 my $realcmd = sub {
2222 my $upid = shift;
2223
b83e0181 2224 my $newvollist = [];
6116f729 2225
b83e0181 2226 eval {
829967a9 2227 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
75466c4f 2228
6116f729
DM
2229 PVE::Storage::activate_volumes($storecfg, $vollist);
2230
829967a9
DM
2231 foreach my $opt (keys %$drives) {
2232 my $drive = $drives->{$opt};
2dd53043 2233
152fe752
DM
2234 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
2235 $newid, $storage, $format, $drive->{full}, $newvollist);
00b095ca 2236
152fe752 2237 $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
2dd53043 2238
829967a9
DM
2239 PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
2240 }
b83e0181
DM
2241
2242 delete $newconf->{lock};
2243 PVE::QemuServer::update_config_nolock($newid, $newconf, 1);
55173c6b
DM
2244
2245 if ($target) {
2246 my $newconffile = PVE::QemuServer::config_file($newid, $target);
2247 die "Failed to move config to node '$target' - rename failed: $!\n"
2248 if !rename($conffile, $newconffile);
2249 }
d703d4c0 2250
be517049 2251 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2252 };
75466c4f 2253 if (my $err = $@) {
6116f729
DM
2254 unlink $conffile;
2255
b83e0181
DM
2256 sleep 1; # some storage like rbd need to wait before release volume - really?
2257
2258 foreach my $volid (@$newvollist) {
2259 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2260 warn $@ if $@;
2261 }
9418baad 2262 die "clone failed: $err";
6116f729
DM
2263 }
2264
2265 return;
2266 };
2267
9418baad 2268 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2269 };
2270
4e4f83fe 2271 return PVE::QemuServer::lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2272 # Aquire exclusive lock lock for $newid
9418baad 2273 return PVE::QemuServer::lock_config_full($newid, 1, $clonefn);
6116f729
DM
2274 });
2275
2276 }});
2277
586bfa78 2278__PACKAGE__->register_method({
43bc02a9
DM
2279 name => 'move_vm_disk',
2280 path => '{vmid}/move_disk',
e2cd75fa 2281 method => 'POST',
586bfa78
AD
2282 protected => 1,
2283 proxyto => 'node',
2284 description => "Move volume to different storage.",
2285 permissions => {
e2cd75fa
DM
2286 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2287 "and 'Datastore.AllocateSpace' permissions on the storage.",
7043d946 2288 check =>
e2cd75fa
DM
2289 [ 'and',
2290 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2291 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2292 ],
586bfa78
AD
2293 },
2294 parameters => {
2295 additionalProperties => 0,
2296 properties => {
2297 node => get_standard_option('pve-node'),
2298 vmid => get_standard_option('pve-vmid'),
586bfa78
AD
2299 disk => {
2300 type => 'string',
2301 description => "The disk you want to move.",
e2cd75fa 2302 enum => [ PVE::QemuServer::disknames() ],
586bfa78 2303 },
e2cd75fa 2304 storage => get_standard_option('pve-storage-id', { description => "Target Storage." }),
635c3c44 2305 'format' => {
586bfa78
AD
2306 type => 'string',
2307 description => "Target Format.",
2308 enum => [ 'raw', 'qcow2', 'vmdk' ],
2309 optional => 1,
2310 },
70d45e33
DM
2311 delete => {
2312 type => 'boolean',
2313 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2314 optional => 1,
2315 default => 0,
2316 },
586bfa78
AD
2317 digest => {
2318 type => 'string',
2319 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2320 maxLength => 40,
2321 optional => 1,
2322 },
2323 },
2324 },
e2cd75fa
DM
2325 returns => {
2326 type => 'string',
2327 description => "the task ID.",
2328 },
586bfa78
AD
2329 code => sub {
2330 my ($param) = @_;
2331
2332 my $rpcenv = PVE::RPCEnvironment::get();
2333
2334 my $authuser = $rpcenv->get_user();
2335
2336 my $node = extract_param($param, 'node');
2337
2338 my $vmid = extract_param($param, 'vmid');
2339
2340 my $digest = extract_param($param, 'digest');
2341
2342 my $disk = extract_param($param, 'disk');
2343
2344 my $storeid = extract_param($param, 'storage');
2345
2346 my $format = extract_param($param, 'format');
2347
586bfa78
AD
2348 my $storecfg = PVE::Storage::config();
2349
2350 my $updatefn = sub {
2351
2352 my $conf = PVE::QemuServer::load_config($vmid);
2353
2354 die "checksum missmatch (file change by other user?)\n"
2355 if $digest && $digest ne $conf->{digest};
586bfa78
AD
2356
2357 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2358
2359 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2360
70d45e33 2361 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78
AD
2362
2363 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2364
e2cd75fa 2365 my $oldfmt;
70d45e33 2366 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
2367 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2368 $oldfmt = $1;
2369 }
2370
7043d946 2371 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 2372 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78
AD
2373
2374 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2375
2376 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
2377
2378 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
2379
586bfa78
AD
2380 my $realcmd = sub {
2381
2382 my $newvollist = [];
2383
2384 eval {
2385 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
2386
e2cd75fa
DM
2387 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2388 $vmid, $storeid, $format, 1, $newvollist);
2389
2390 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2391
70d45e33 2392 PVE::QemuServer::add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2393
e2cd75fa 2394 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
586bfa78
AD
2395 };
2396 if (my $err = $@) {
2397
2398 foreach my $volid (@$newvollist) {
2399 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2400 warn $@ if $@;
2401 }
2402 die "storage migration failed: $err";
2403 }
70d45e33
DM
2404
2405 if ($param->{delete}) {
2406 eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
2407 warn $@ if $@;
2408 }
586bfa78
AD
2409 };
2410
2411 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2412 };
e2cd75fa
DM
2413
2414 return PVE::QemuServer::lock_config($vmid, $updatefn);
586bfa78
AD
2415 }});
2416
3ea94c60 2417__PACKAGE__->register_method({
afdb31d5 2418 name => 'migrate_vm',
3ea94c60
DM
2419 path => '{vmid}/migrate',
2420 method => 'POST',
2421 protected => 1,
2422 proxyto => 'node',
2423 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2424 permissions => {
2425 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2426 },
3ea94c60
DM
2427 parameters => {
2428 additionalProperties => 0,
2429 properties => {
2430 node => get_standard_option('pve-node'),
2431 vmid => get_standard_option('pve-vmid'),
2432 target => get_standard_option('pve-node', { description => "Target node." }),
2433 online => {
2434 type => 'boolean',
2435 description => "Use online/live migration.",
2436 optional => 1,
2437 },
2438 force => {
2439 type => 'boolean',
2440 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2441 optional => 1,
2442 },
2443 },
2444 },
afdb31d5 2445 returns => {
3ea94c60
DM
2446 type => 'string',
2447 description => "the task ID.",
2448 },
2449 code => sub {
2450 my ($param) = @_;
2451
2452 my $rpcenv = PVE::RPCEnvironment::get();
2453
a0d1b1a2 2454 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2455
2456 my $target = extract_param($param, 'target');
2457
2458 my $localnode = PVE::INotify::nodename();
2459 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2460
2461 PVE::Cluster::check_cfs_quorum();
2462
2463 PVE::Cluster::check_node_exists($target);
2464
2465 my $targetip = PVE::Cluster::remote_node_ip($target);
2466
2467 my $vmid = extract_param($param, 'vmid');
2468
afdb31d5 2469 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2470 if $param->{force} && $authuser ne 'root@pam';
3ea94c60
DM
2471
2472 # test if VM exists
a5ed42d3 2473 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
2474
2475 # try to detect errors early
a5ed42d3
DM
2476
2477 PVE::QemuServer::check_lock($conf);
2478
3ea94c60 2479 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2480 die "cant migrate running VM without --online\n"
3ea94c60
DM
2481 if !$param->{online};
2482 }
2483
47152e2e 2484 my $storecfg = PVE::Storage::config();
22d646a7 2485 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
47152e2e 2486
3be30d63 2487 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2488
88fc87b4
DM
2489 my $hacmd = sub {
2490 my $upid = shift;
3ea94c60 2491
88fc87b4
DM
2492 my $service = "pvevm:$vmid";
2493
2494 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2495
2496 print "Executing HA migrate for VM $vmid to node $target\n";
2497
2498 PVE::Tools::run_command($cmd);
2499
2500 return;
2501 };
2502
2503 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2504
2505 } else {
2506
2507 my $realcmd = sub {
2508 my $upid = shift;
2509
2510 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2511 };
2512
2513 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2514 }
3ea94c60 2515
3ea94c60 2516 }});
1e3baf05 2517
91c94f0a 2518__PACKAGE__->register_method({
afdb31d5
DM
2519 name => 'monitor',
2520 path => '{vmid}/monitor',
91c94f0a
DM
2521 method => 'POST',
2522 protected => 1,
2523 proxyto => 'node',
2524 description => "Execute Qemu monitor commands.",
a0d1b1a2
DM
2525 permissions => {
2526 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2527 },
91c94f0a
DM
2528 parameters => {
2529 additionalProperties => 0,
2530 properties => {
2531 node => get_standard_option('pve-node'),
2532 vmid => get_standard_option('pve-vmid'),
2533 command => {
2534 type => 'string',
2535 description => "The monitor command.",
2536 }
2537 },
2538 },
2539 returns => { type => 'string'},
2540 code => sub {
2541 my ($param) = @_;
2542
2543 my $vmid = $param->{vmid};
2544
2545 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
2546
2547 my $res = '';
2548 eval {
7b7c6d1b 2549 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2550 };
2551 $res = "ERROR: $@" if $@;
2552
2553 return $res;
2554 }});
2555
0d02881c
AD
2556__PACKAGE__->register_method({
2557 name => 'resize_vm',
614e3941 2558 path => '{vmid}/resize',
0d02881c
AD
2559 method => 'PUT',
2560 protected => 1,
2561 proxyto => 'node',
2f48a4f5 2562 description => "Extend volume size.",
0d02881c 2563 permissions => {
3b2773f6 2564 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
2565 },
2566 parameters => {
2567 additionalProperties => 0,
2f48a4f5
DM
2568 properties => {
2569 node => get_standard_option('pve-node'),
2570 vmid => get_standard_option('pve-vmid'),
2571 skiplock => get_standard_option('skiplock'),
2572 disk => {
2573 type => 'string',
2574 description => "The disk you want to resize.",
2575 enum => [PVE::QemuServer::disknames()],
2576 },
2577 size => {
2578 type => 'string',
f91b2e45 2579 pattern => '\+?\d+(\.\d+)?[KMGT]?',
2f48a4f5
DM
2580 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.",
2581 },
2582 digest => {
2583 type => 'string',
2584 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2585 maxLength => 40,
2586 optional => 1,
2587 },
2588 },
0d02881c
AD
2589 },
2590 returns => { type => 'null'},
2591 code => sub {
2592 my ($param) = @_;
2593
2594 my $rpcenv = PVE::RPCEnvironment::get();
2595
2596 my $authuser = $rpcenv->get_user();
2597
2598 my $node = extract_param($param, 'node');
2599
2600 my $vmid = extract_param($param, 'vmid');
2601
2602 my $digest = extract_param($param, 'digest');
2603
2f48a4f5 2604 my $disk = extract_param($param, 'disk');
75466c4f 2605
2f48a4f5 2606 my $sizestr = extract_param($param, 'size');
0d02881c 2607
f91b2e45 2608 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
2609 raise_param_exc({ skiplock => "Only root may use this option." })
2610 if $skiplock && $authuser ne 'root@pam';
2611
0d02881c
AD
2612 my $storecfg = PVE::Storage::config();
2613
0d02881c
AD
2614 my $updatefn = sub {
2615
2616 my $conf = PVE::QemuServer::load_config($vmid);
2617
2618 die "checksum missmatch (file change by other user?)\n"
2619 if $digest && $digest ne $conf->{digest};
2620 PVE::QemuServer::check_lock($conf) if !$skiplock;
2621
f91b2e45
DM
2622 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2623
2624 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2625
2626 my $volid = $drive->{file};
2627
2628 die "disk '$disk' has no associated volume\n" if !$volid;
2629
2630 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2631
75466c4f 2632 die "you can't online resize a virtio windows bootdisk\n"
f2965e67
AD
2633 if PVE::QemuServer::check_running($vmid) && $conf->{bootdisk} eq $disk && $conf->{ostype} =~ m/^w/ && $disk =~ m/^virtio/;
2634
f91b2e45
DM
2635 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
2636
2637 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2638
2639 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
2640
2641 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2642 my ($ext, $newsize, $unit) = ($1, $2, $4);
2643 if ($unit) {
2644 if ($unit eq 'K') {
2645 $newsize = $newsize * 1024;
2646 } elsif ($unit eq 'M') {
2647 $newsize = $newsize * 1024 * 1024;
2648 } elsif ($unit eq 'G') {
2649 $newsize = $newsize * 1024 * 1024 * 1024;
2650 } elsif ($unit eq 'T') {
2651 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2652 }
2653 }
2654 $newsize += $size if $ext;
2655 $newsize = int($newsize);
2656
2657 die "unable to skrink disk size\n" if $newsize < $size;
2658
2659 return if $size == $newsize;
2660
2f48a4f5 2661 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 2662
f91b2e45 2663 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 2664
f91b2e45
DM
2665 $drive->{size} = $newsize;
2666 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
2667
2668 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2669 };
0d02881c
AD
2670
2671 PVE::QemuServer::lock_config($vmid, $updatefn);
2672 return undef;
2673 }});
2674
9dbd1ee4 2675__PACKAGE__->register_method({
7e7d7b61 2676 name => 'snapshot_list',
9dbd1ee4 2677 path => '{vmid}/snapshot',
7e7d7b61
DM
2678 method => 'GET',
2679 description => "List all snapshots.",
2680 permissions => {
2681 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2682 },
2683 proxyto => 'node',
2684 protected => 1, # qemu pid files are only readable by root
2685 parameters => {
2686 additionalProperties => 0,
2687 properties => {
2688 vmid => get_standard_option('pve-vmid'),
2689 node => get_standard_option('pve-node'),
2690 },
2691 },
2692 returns => {
2693 type => 'array',
2694 items => {
2695 type => "object",
2696 properties => {},
2697 },
2698 links => [ { rel => 'child', href => "{name}" } ],
2699 },
2700 code => sub {
2701 my ($param) = @_;
2702
6aa4651b
DM
2703 my $vmid = $param->{vmid};
2704
2705 my $conf = PVE::QemuServer::load_config($vmid);
7e7d7b61
DM
2706 my $snaphash = $conf->{snapshots} || {};
2707
2708 my $res = [];
2709
2710 foreach my $name (keys %$snaphash) {
0ea6bc69 2711 my $d = $snaphash->{$name};
75466c4f
DM
2712 my $item = {
2713 name => $name,
2714 snaptime => $d->{snaptime} || 0,
6aa4651b 2715 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
2716 description => $d->{description} || '',
2717 };
0ea6bc69 2718 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 2719 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
2720 push @$res, $item;
2721 }
2722
6aa4651b
DM
2723 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
2724 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
2725 $current->{parent} = $conf->{parent} if $conf->{parent};
2726
2727 push @$res, $current;
7e7d7b61
DM
2728
2729 return $res;
2730 }});
2731
2732__PACKAGE__->register_method({
2733 name => 'snapshot',
2734 path => '{vmid}/snapshot',
2735 method => 'POST',
9dbd1ee4
AD
2736 protected => 1,
2737 proxyto => 'node',
2738 description => "Snapshot a VM.",
2739 permissions => {
f1baf1df 2740 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
2741 },
2742 parameters => {
2743 additionalProperties => 0,
2744 properties => {
2745 node => get_standard_option('pve-node'),
2746 vmid => get_standard_option('pve-vmid'),
8abd398b 2747 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
2748 vmstate => {
2749 optional => 1,
2750 type => 'boolean',
2751 description => "Save the vmstate",
2752 },
2753 freezefs => {
2754 optional => 1,
2755 type => 'boolean',
2756 description => "Freeze the filesystem",
2757 },
782f4f75
DM
2758 description => {
2759 optional => 1,
2760 type => 'string',
2761 description => "A textual description or comment.",
2762 },
9dbd1ee4
AD
2763 },
2764 },
7e7d7b61
DM
2765 returns => {
2766 type => 'string',
2767 description => "the task ID.",
2768 },
9dbd1ee4
AD
2769 code => sub {
2770 my ($param) = @_;
2771
2772 my $rpcenv = PVE::RPCEnvironment::get();
2773
2774 my $authuser = $rpcenv->get_user();
2775
2776 my $node = extract_param($param, 'node');
2777
2778 my $vmid = extract_param($param, 'vmid');
2779
9dbd1ee4
AD
2780 my $snapname = extract_param($param, 'snapname');
2781
d1914468
DM
2782 die "unable to use snapshot name 'current' (reserved name)\n"
2783 if $snapname eq 'current';
2784
7e7d7b61 2785 my $realcmd = sub {
22c377f0 2786 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
75466c4f 2787 PVE::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate},
782f4f75 2788 $param->{freezefs}, $param->{description});
7e7d7b61
DM
2789 };
2790
2791 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2792 }});
2793
154ccdcd
DM
2794__PACKAGE__->register_method({
2795 name => 'snapshot_cmd_idx',
2796 path => '{vmid}/snapshot/{snapname}',
2797 description => '',
2798 method => 'GET',
2799 permissions => {
2800 user => 'all',
2801 },
2802 parameters => {
2803 additionalProperties => 0,
2804 properties => {
2805 vmid => get_standard_option('pve-vmid'),
2806 node => get_standard_option('pve-node'),
8abd398b 2807 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
2808 },
2809 },
2810 returns => {
2811 type => 'array',
2812 items => {
2813 type => "object",
2814 properties => {},
2815 },
2816 links => [ { rel => 'child', href => "{cmd}" } ],
2817 },
2818 code => sub {
2819 my ($param) = @_;
2820
2821 my $res = [];
2822
2823 push @$res, { cmd => 'rollback' };
d788cea6 2824 push @$res, { cmd => 'config' };
154ccdcd
DM
2825
2826 return $res;
2827 }});
2828
d788cea6
DM
2829__PACKAGE__->register_method({
2830 name => 'update_snapshot_config',
2831 path => '{vmid}/snapshot/{snapname}/config',
2832 method => 'PUT',
2833 protected => 1,
2834 proxyto => 'node',
2835 description => "Update snapshot metadata.",
2836 permissions => {
2837 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2838 },
2839 parameters => {
2840 additionalProperties => 0,
2841 properties => {
2842 node => get_standard_option('pve-node'),
2843 vmid => get_standard_option('pve-vmid'),
2844 snapname => get_standard_option('pve-snapshot-name'),
2845 description => {
2846 optional => 1,
2847 type => 'string',
2848 description => "A textual description or comment.",
2849 },
2850 },
2851 },
2852 returns => { type => 'null' },
2853 code => sub {
2854 my ($param) = @_;
2855
2856 my $rpcenv = PVE::RPCEnvironment::get();
2857
2858 my $authuser = $rpcenv->get_user();
2859
2860 my $vmid = extract_param($param, 'vmid');
2861
2862 my $snapname = extract_param($param, 'snapname');
2863
2864 return undef if !defined($param->{description});
2865
2866 my $updatefn = sub {
2867
2868 my $conf = PVE::QemuServer::load_config($vmid);
2869
2870 PVE::QemuServer::check_lock($conf);
2871
2872 my $snap = $conf->{snapshots}->{$snapname};
2873
75466c4f
DM
2874 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2875
d788cea6
DM
2876 $snap->{description} = $param->{description} if defined($param->{description});
2877
2878 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2879 };
2880
2881 PVE::QemuServer::lock_config($vmid, $updatefn);
2882
2883 return undef;
2884 }});
2885
2886__PACKAGE__->register_method({
2887 name => 'get_snapshot_config',
2888 path => '{vmid}/snapshot/{snapname}/config',
2889 method => 'GET',
2890 proxyto => 'node',
2891 description => "Get snapshot configuration",
2892 permissions => {
2893 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2894 },
2895 parameters => {
2896 additionalProperties => 0,
2897 properties => {
2898 node => get_standard_option('pve-node'),
2899 vmid => get_standard_option('pve-vmid'),
2900 snapname => get_standard_option('pve-snapshot-name'),
2901 },
2902 },
2903 returns => { type => "object" },
2904 code => sub {
2905 my ($param) = @_;
2906
2907 my $rpcenv = PVE::RPCEnvironment::get();
2908
2909 my $authuser = $rpcenv->get_user();
2910
2911 my $vmid = extract_param($param, 'vmid');
2912
2913 my $snapname = extract_param($param, 'snapname');
2914
2915 my $conf = PVE::QemuServer::load_config($vmid);
2916
2917 my $snap = $conf->{snapshots}->{$snapname};
2918
75466c4f
DM
2919 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2920
d788cea6
DM
2921 return $snap;
2922 }});
2923
7e7d7b61
DM
2924__PACKAGE__->register_method({
2925 name => 'rollback',
154ccdcd 2926 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
2927 method => 'POST',
2928 protected => 1,
2929 proxyto => 'node',
2930 description => "Rollback VM state to specified snapshot.",
2931 permissions => {
f1baf1df 2932 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2933 },
2934 parameters => {
2935 additionalProperties => 0,
2936 properties => {
2937 node => get_standard_option('pve-node'),
2938 vmid => get_standard_option('pve-vmid'),
8abd398b 2939 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
2940 },
2941 },
2942 returns => {
2943 type => 'string',
2944 description => "the task ID.",
2945 },
2946 code => sub {
2947 my ($param) = @_;
2948
2949 my $rpcenv = PVE::RPCEnvironment::get();
2950
2951 my $authuser = $rpcenv->get_user();
2952
2953 my $node = extract_param($param, 'node');
2954
2955 my $vmid = extract_param($param, 'vmid');
2956
2957 my $snapname = extract_param($param, 'snapname');
2958
7e7d7b61 2959 my $realcmd = sub {
22c377f0
DM
2960 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2961 PVE::QemuServer::snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
2962 };
2963
2964 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2965 }});
2966
2967__PACKAGE__->register_method({
2968 name => 'delsnapshot',
2969 path => '{vmid}/snapshot/{snapname}',
2970 method => 'DELETE',
2971 protected => 1,
2972 proxyto => 'node',
2973 description => "Delete a VM snapshot.",
2974 permissions => {
f1baf1df 2975 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2976 },
2977 parameters => {
2978 additionalProperties => 0,
2979 properties => {
2980 node => get_standard_option('pve-node'),
2981 vmid => get_standard_option('pve-vmid'),
8abd398b 2982 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
2983 force => {
2984 optional => 1,
2985 type => 'boolean',
2986 description => "For removal from config file, even if removing disk snapshots fails.",
2987 },
7e7d7b61
DM
2988 },
2989 },
2990 returns => {
2991 type => 'string',
2992 description => "the task ID.",
2993 },
2994 code => sub {
2995 my ($param) = @_;
2996
2997 my $rpcenv = PVE::RPCEnvironment::get();
2998
2999 my $authuser = $rpcenv->get_user();
3000
3001 my $node = extract_param($param, 'node');
3002
3003 my $vmid = extract_param($param, 'vmid');
3004
3005 my $snapname = extract_param($param, 'snapname');
3006
7e7d7b61 3007 my $realcmd = sub {
22c377f0 3008 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
3ee28e38 3009 PVE::QemuServer::snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3010 };
9dbd1ee4 3011
7b2257a8 3012 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3013 }});
3014
04a69bb4
AD
3015__PACKAGE__->register_method({
3016 name => 'template',
3017 path => '{vmid}/template',
3018 method => 'POST',
3019 protected => 1,
3020 proxyto => 'node',
3021 description => "Create a Template.",
b02691d8 3022 permissions => {
7af0a6c8
DM
3023 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3024 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3025 },
04a69bb4
AD
3026 parameters => {
3027 additionalProperties => 0,
3028 properties => {
3029 node => get_standard_option('pve-node'),
3030 vmid => get_standard_option('pve-vmid'),
3031 disk => {
3032 optional => 1,
3033 type => 'string',
3034 description => "If you want to convert only 1 disk to base image.",
3035 enum => [PVE::QemuServer::disknames()],
3036 },
3037
3038 },
3039 },
3040 returns => { type => 'null'},
3041 code => sub {
3042 my ($param) = @_;
3043
3044 my $rpcenv = PVE::RPCEnvironment::get();
3045
3046 my $authuser = $rpcenv->get_user();
3047
3048 my $node = extract_param($param, 'node');
3049
3050 my $vmid = extract_param($param, 'vmid');
3051
3052 my $disk = extract_param($param, 'disk');
3053
3054 my $updatefn = sub {
3055
3056 my $conf = PVE::QemuServer::load_config($vmid);
3057
3058 PVE::QemuServer::check_lock($conf);
3059
75466c4f 3060 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3061 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3062
75466c4f 3063 die "you can't convert a template to a template\n"
03c2d0ad 3064 if PVE::QemuServer::is_template($conf) && !$disk;
0402a80b 3065
75466c4f 3066 die "you can't convert a VM to template if VM is running\n"
218cab9a 3067 if PVE::QemuServer::check_running($vmid);
35c5fdef 3068
04a69bb4
AD
3069 my $realcmd = sub {
3070 PVE::QemuServer::template_create($vmid, $conf, $disk);
3071 };
04a69bb4 3072
75e7e997 3073 $conf->{template} = 1;
04a69bb4 3074 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
75e7e997
DM
3075
3076 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3077 };
3078
3079 PVE::QemuServer::lock_config($vmid, $updatefn);
3080 return undef;
3081 }});
3082
1e3baf05 30831;