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