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