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