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