]> git.proxmox.com Git - qemu-server.git/blame - PVE/API2/Qemu.pm
bump version to 3.1-9
[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) {
baca276d
DM
2255 # always deactivate volumes - avoid lvm LVs to be active on several nodes
2256 PVE::Storage::deactivate_volumes($storecfg, $vollist);
2257
55173c6b
DM
2258 my $newconffile = PVE::QemuServer::config_file($newid, $target);
2259 die "Failed to move config to node '$target' - rename failed: $!\n"
2260 if !rename($conffile, $newconffile);
2261 }
d703d4c0 2262
be517049 2263 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
6116f729 2264 };
75466c4f 2265 if (my $err = $@) {
6116f729
DM
2266 unlink $conffile;
2267
b83e0181
DM
2268 sleep 1; # some storage like rbd need to wait before release volume - really?
2269
2270 foreach my $volid (@$newvollist) {
2271 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2272 warn $@ if $@;
2273 }
9418baad 2274 die "clone failed: $err";
6116f729
DM
2275 }
2276
2277 return;
2278 };
2279
9418baad 2280 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
6116f729
DM
2281 };
2282
4e4f83fe 2283 return PVE::QemuServer::lock_config_mode($vmid, 1, $shared_lock, sub {
6116f729 2284 # Aquire exclusive lock lock for $newid
9418baad 2285 return PVE::QemuServer::lock_config_full($newid, 1, $clonefn);
6116f729
DM
2286 });
2287
2288 }});
2289
586bfa78 2290__PACKAGE__->register_method({
43bc02a9
DM
2291 name => 'move_vm_disk',
2292 path => '{vmid}/move_disk',
e2cd75fa 2293 method => 'POST',
586bfa78
AD
2294 protected => 1,
2295 proxyto => 'node',
2296 description => "Move volume to different storage.",
2297 permissions => {
e2cd75fa
DM
2298 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2299 "and 'Datastore.AllocateSpace' permissions on the storage.",
7043d946 2300 check =>
e2cd75fa
DM
2301 [ 'and',
2302 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2303 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
2304 ],
586bfa78
AD
2305 },
2306 parameters => {
2307 additionalProperties => 0,
2308 properties => {
2309 node => get_standard_option('pve-node'),
2310 vmid => get_standard_option('pve-vmid'),
586bfa78
AD
2311 disk => {
2312 type => 'string',
2313 description => "The disk you want to move.",
e2cd75fa 2314 enum => [ PVE::QemuServer::disknames() ],
586bfa78 2315 },
e2cd75fa 2316 storage => get_standard_option('pve-storage-id', { description => "Target Storage." }),
635c3c44 2317 'format' => {
586bfa78
AD
2318 type => 'string',
2319 description => "Target Format.",
2320 enum => [ 'raw', 'qcow2', 'vmdk' ],
2321 optional => 1,
2322 },
70d45e33
DM
2323 delete => {
2324 type => 'boolean',
2325 description => "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
2326 optional => 1,
2327 default => 0,
2328 },
586bfa78
AD
2329 digest => {
2330 type => 'string',
2331 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2332 maxLength => 40,
2333 optional => 1,
2334 },
2335 },
2336 },
e2cd75fa
DM
2337 returns => {
2338 type => 'string',
2339 description => "the task ID.",
2340 },
586bfa78
AD
2341 code => sub {
2342 my ($param) = @_;
2343
2344 my $rpcenv = PVE::RPCEnvironment::get();
2345
2346 my $authuser = $rpcenv->get_user();
2347
2348 my $node = extract_param($param, 'node');
2349
2350 my $vmid = extract_param($param, 'vmid');
2351
2352 my $digest = extract_param($param, 'digest');
2353
2354 my $disk = extract_param($param, 'disk');
2355
2356 my $storeid = extract_param($param, 'storage');
2357
2358 my $format = extract_param($param, 'format');
2359
586bfa78
AD
2360 my $storecfg = PVE::Storage::config();
2361
2362 my $updatefn = sub {
2363
2364 my $conf = PVE::QemuServer::load_config($vmid);
2365
2366 die "checksum missmatch (file change by other user?)\n"
2367 if $digest && $digest ne $conf->{digest};
586bfa78
AD
2368
2369 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2370
2371 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2372
70d45e33 2373 my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
586bfa78
AD
2374
2375 die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2376
e2cd75fa 2377 my $oldfmt;
70d45e33 2378 my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
586bfa78
AD
2379 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
2380 $oldfmt = $1;
2381 }
2382
7043d946 2383 die "you can't move on the same storage with same format\n" if $oldstoreid eq $storeid &&
e2cd75fa 2384 (!$format || !$oldfmt || $oldfmt eq $format);
586bfa78
AD
2385
2386 PVE::Cluster::log_msg('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
2387
2388 my $running = PVE::QemuServer::check_running($vmid);
e2cd75fa
DM
2389
2390 PVE::Storage::activate_volumes($storecfg, [ $drive->{file} ]);
2391
586bfa78
AD
2392 my $realcmd = sub {
2393
2394 my $newvollist = [];
2395
2396 eval {
2397 local $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = sub { die "interrupted by signal\n"; };
2398
e2cd75fa
DM
2399 my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
2400 $vmid, $storeid, $format, 1, $newvollist);
2401
2402 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
2403
70d45e33 2404 PVE::QemuServer::add_unused_volume($conf, $old_volid) if !$param->{delete};
7043d946 2405
e2cd75fa 2406 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
586bfa78
AD
2407 };
2408 if (my $err = $@) {
2409
2410 foreach my $volid (@$newvollist) {
2411 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2412 warn $@ if $@;
2413 }
2414 die "storage migration failed: $err";
2415 }
70d45e33
DM
2416
2417 if ($param->{delete}) {
2418 eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
2419 warn $@ if $@;
2420 }
586bfa78
AD
2421 };
2422
2423 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
2424 };
e2cd75fa
DM
2425
2426 return PVE::QemuServer::lock_config($vmid, $updatefn);
586bfa78
AD
2427 }});
2428
3ea94c60 2429__PACKAGE__->register_method({
afdb31d5 2430 name => 'migrate_vm',
3ea94c60
DM
2431 path => '{vmid}/migrate',
2432 method => 'POST',
2433 protected => 1,
2434 proxyto => 'node',
2435 description => "Migrate virtual machine. Creates a new migration task.",
a0d1b1a2
DM
2436 permissions => {
2437 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
2438 },
3ea94c60
DM
2439 parameters => {
2440 additionalProperties => 0,
2441 properties => {
2442 node => get_standard_option('pve-node'),
2443 vmid => get_standard_option('pve-vmid'),
2444 target => get_standard_option('pve-node', { description => "Target node." }),
2445 online => {
2446 type => 'boolean',
2447 description => "Use online/live migration.",
2448 optional => 1,
2449 },
2450 force => {
2451 type => 'boolean',
2452 description => "Allow to migrate VMs which use local devices. Only root may use this option.",
2453 optional => 1,
2454 },
2455 },
2456 },
afdb31d5 2457 returns => {
3ea94c60
DM
2458 type => 'string',
2459 description => "the task ID.",
2460 },
2461 code => sub {
2462 my ($param) = @_;
2463
2464 my $rpcenv = PVE::RPCEnvironment::get();
2465
a0d1b1a2 2466 my $authuser = $rpcenv->get_user();
3ea94c60
DM
2467
2468 my $target = extract_param($param, 'target');
2469
2470 my $localnode = PVE::INotify::nodename();
2471 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
2472
2473 PVE::Cluster::check_cfs_quorum();
2474
2475 PVE::Cluster::check_node_exists($target);
2476
2477 my $targetip = PVE::Cluster::remote_node_ip($target);
2478
2479 my $vmid = extract_param($param, 'vmid');
2480
afdb31d5 2481 raise_param_exc({ force => "Only root may use this option." })
a0d1b1a2 2482 if $param->{force} && $authuser ne 'root@pam';
3ea94c60
DM
2483
2484 # test if VM exists
a5ed42d3 2485 my $conf = PVE::QemuServer::load_config($vmid);
3ea94c60
DM
2486
2487 # try to detect errors early
a5ed42d3
DM
2488
2489 PVE::QemuServer::check_lock($conf);
2490
3ea94c60 2491 if (PVE::QemuServer::check_running($vmid)) {
afdb31d5 2492 die "cant migrate running VM without --online\n"
3ea94c60
DM
2493 if !$param->{online};
2494 }
2495
47152e2e 2496 my $storecfg = PVE::Storage::config();
22d646a7 2497 PVE::QemuServer::check_storage_availability($storecfg, $conf, $target);
47152e2e 2498
3be30d63 2499 if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
3ea94c60 2500
88fc87b4
DM
2501 my $hacmd = sub {
2502 my $upid = shift;
3ea94c60 2503
88fc87b4
DM
2504 my $service = "pvevm:$vmid";
2505
2506 my $cmd = ['clusvcadm', '-M', $service, '-m', $target];
2507
2508 print "Executing HA migrate for VM $vmid to node $target\n";
2509
2510 PVE::Tools::run_command($cmd);
2511
2512 return;
2513 };
2514
2515 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
2516
2517 } else {
2518
2519 my $realcmd = sub {
2520 my $upid = shift;
2521
2522 PVE::QemuMigrate->migrate($target, $targetip, $vmid, $param);
2523 };
2524
2525 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $realcmd);
2526 }
3ea94c60 2527
3ea94c60 2528 }});
1e3baf05 2529
91c94f0a 2530__PACKAGE__->register_method({
afdb31d5
DM
2531 name => 'monitor',
2532 path => '{vmid}/monitor',
91c94f0a
DM
2533 method => 'POST',
2534 protected => 1,
2535 proxyto => 'node',
2536 description => "Execute Qemu monitor commands.",
a0d1b1a2
DM
2537 permissions => {
2538 check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
2539 },
91c94f0a
DM
2540 parameters => {
2541 additionalProperties => 0,
2542 properties => {
2543 node => get_standard_option('pve-node'),
2544 vmid => get_standard_option('pve-vmid'),
2545 command => {
2546 type => 'string',
2547 description => "The monitor command.",
2548 }
2549 },
2550 },
2551 returns => { type => 'string'},
2552 code => sub {
2553 my ($param) = @_;
2554
2555 my $vmid = $param->{vmid};
2556
2557 my $conf = PVE::QemuServer::load_config ($vmid); # check if VM exists
2558
2559 my $res = '';
2560 eval {
7b7c6d1b 2561 $res = PVE::QemuServer::vm_human_monitor_command($vmid, $param->{command});
91c94f0a
DM
2562 };
2563 $res = "ERROR: $@" if $@;
2564
2565 return $res;
2566 }});
2567
0d02881c
AD
2568__PACKAGE__->register_method({
2569 name => 'resize_vm',
614e3941 2570 path => '{vmid}/resize',
0d02881c
AD
2571 method => 'PUT',
2572 protected => 1,
2573 proxyto => 'node',
2f48a4f5 2574 description => "Extend volume size.",
0d02881c 2575 permissions => {
3b2773f6 2576 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
0d02881c
AD
2577 },
2578 parameters => {
2579 additionalProperties => 0,
2f48a4f5
DM
2580 properties => {
2581 node => get_standard_option('pve-node'),
2582 vmid => get_standard_option('pve-vmid'),
2583 skiplock => get_standard_option('skiplock'),
2584 disk => {
2585 type => 'string',
2586 description => "The disk you want to resize.",
2587 enum => [PVE::QemuServer::disknames()],
2588 },
2589 size => {
2590 type => 'string',
f91b2e45 2591 pattern => '\+?\d+(\.\d+)?[KMGT]?',
2f48a4f5
DM
2592 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.",
2593 },
2594 digest => {
2595 type => 'string',
2596 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
2597 maxLength => 40,
2598 optional => 1,
2599 },
2600 },
0d02881c
AD
2601 },
2602 returns => { type => 'null'},
2603 code => sub {
2604 my ($param) = @_;
2605
2606 my $rpcenv = PVE::RPCEnvironment::get();
2607
2608 my $authuser = $rpcenv->get_user();
2609
2610 my $node = extract_param($param, 'node');
2611
2612 my $vmid = extract_param($param, 'vmid');
2613
2614 my $digest = extract_param($param, 'digest');
2615
2f48a4f5 2616 my $disk = extract_param($param, 'disk');
75466c4f 2617
2f48a4f5 2618 my $sizestr = extract_param($param, 'size');
0d02881c 2619
f91b2e45 2620 my $skiplock = extract_param($param, 'skiplock');
0d02881c
AD
2621 raise_param_exc({ skiplock => "Only root may use this option." })
2622 if $skiplock && $authuser ne 'root@pam';
2623
0d02881c
AD
2624 my $storecfg = PVE::Storage::config();
2625
0d02881c
AD
2626 my $updatefn = sub {
2627
2628 my $conf = PVE::QemuServer::load_config($vmid);
2629
2630 die "checksum missmatch (file change by other user?)\n"
2631 if $digest && $digest ne $conf->{digest};
2632 PVE::QemuServer::check_lock($conf) if !$skiplock;
2633
f91b2e45
DM
2634 die "disk '$disk' does not exist\n" if !$conf->{$disk};
2635
2636 my $drive = PVE::QemuServer::parse_drive($disk, $conf->{$disk});
2637
2638 my $volid = $drive->{file};
2639
2640 die "disk '$disk' has no associated volume\n" if !$volid;
2641
2642 die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
2643
75466c4f 2644 die "you can't online resize a virtio windows bootdisk\n"
f2965e67
AD
2645 if PVE::QemuServer::check_running($vmid) && $conf->{bootdisk} eq $disk && $conf->{ostype} =~ m/^w/ && $disk =~ m/^virtio/;
2646
f91b2e45
DM
2647 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
2648
2649 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2650
2651 my $size = PVE::Storage::volume_size_info($storecfg, $volid, 5);
2652
2653 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
2654 my ($ext, $newsize, $unit) = ($1, $2, $4);
2655 if ($unit) {
2656 if ($unit eq 'K') {
2657 $newsize = $newsize * 1024;
2658 } elsif ($unit eq 'M') {
2659 $newsize = $newsize * 1024 * 1024;
2660 } elsif ($unit eq 'G') {
2661 $newsize = $newsize * 1024 * 1024 * 1024;
2662 } elsif ($unit eq 'T') {
2663 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
2664 }
2665 }
2666 $newsize += $size if $ext;
2667 $newsize = int($newsize);
2668
2669 die "unable to skrink disk size\n" if $newsize < $size;
2670
2671 return if $size == $newsize;
2672
2f48a4f5 2673 PVE::Cluster::log_msg('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
0d02881c 2674
f91b2e45 2675 PVE::QemuServer::qemu_block_resize($vmid, "drive-$disk", $storecfg, $volid, $newsize);
75466c4f 2676
f91b2e45
DM
2677 $drive->{size} = $newsize;
2678 $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $drive);
2679
2680 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2681 };
0d02881c
AD
2682
2683 PVE::QemuServer::lock_config($vmid, $updatefn);
2684 return undef;
2685 }});
2686
9dbd1ee4 2687__PACKAGE__->register_method({
7e7d7b61 2688 name => 'snapshot_list',
9dbd1ee4 2689 path => '{vmid}/snapshot',
7e7d7b61
DM
2690 method => 'GET',
2691 description => "List all snapshots.",
2692 permissions => {
2693 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2694 },
2695 proxyto => 'node',
2696 protected => 1, # qemu pid files are only readable by root
2697 parameters => {
2698 additionalProperties => 0,
2699 properties => {
2700 vmid => get_standard_option('pve-vmid'),
2701 node => get_standard_option('pve-node'),
2702 },
2703 },
2704 returns => {
2705 type => 'array',
2706 items => {
2707 type => "object",
2708 properties => {},
2709 },
2710 links => [ { rel => 'child', href => "{name}" } ],
2711 },
2712 code => sub {
2713 my ($param) = @_;
2714
6aa4651b
DM
2715 my $vmid = $param->{vmid};
2716
2717 my $conf = PVE::QemuServer::load_config($vmid);
7e7d7b61
DM
2718 my $snaphash = $conf->{snapshots} || {};
2719
2720 my $res = [];
2721
2722 foreach my $name (keys %$snaphash) {
0ea6bc69 2723 my $d = $snaphash->{$name};
75466c4f
DM
2724 my $item = {
2725 name => $name,
2726 snaptime => $d->{snaptime} || 0,
6aa4651b 2727 vmstate => $d->{vmstate} ? 1 : 0,
982c7f12
DM
2728 description => $d->{description} || '',
2729 };
0ea6bc69 2730 $item->{parent} = $d->{parent} if $d->{parent};
3ee28e38 2731 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
0ea6bc69
DM
2732 push @$res, $item;
2733 }
2734
6aa4651b
DM
2735 my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
2736 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
d1914468
DM
2737 $current->{parent} = $conf->{parent} if $conf->{parent};
2738
2739 push @$res, $current;
7e7d7b61
DM
2740
2741 return $res;
2742 }});
2743
2744__PACKAGE__->register_method({
2745 name => 'snapshot',
2746 path => '{vmid}/snapshot',
2747 method => 'POST',
9dbd1ee4
AD
2748 protected => 1,
2749 proxyto => 'node',
2750 description => "Snapshot a VM.",
2751 permissions => {
f1baf1df 2752 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
9dbd1ee4
AD
2753 },
2754 parameters => {
2755 additionalProperties => 0,
2756 properties => {
2757 node => get_standard_option('pve-node'),
2758 vmid => get_standard_option('pve-vmid'),
8abd398b 2759 snapname => get_standard_option('pve-snapshot-name'),
9dbd1ee4
AD
2760 vmstate => {
2761 optional => 1,
2762 type => 'boolean',
2763 description => "Save the vmstate",
2764 },
2765 freezefs => {
2766 optional => 1,
2767 type => 'boolean',
2768 description => "Freeze the filesystem",
2769 },
782f4f75
DM
2770 description => {
2771 optional => 1,
2772 type => 'string',
2773 description => "A textual description or comment.",
2774 },
9dbd1ee4
AD
2775 },
2776 },
7e7d7b61
DM
2777 returns => {
2778 type => 'string',
2779 description => "the task ID.",
2780 },
9dbd1ee4
AD
2781 code => sub {
2782 my ($param) = @_;
2783
2784 my $rpcenv = PVE::RPCEnvironment::get();
2785
2786 my $authuser = $rpcenv->get_user();
2787
2788 my $node = extract_param($param, 'node');
2789
2790 my $vmid = extract_param($param, 'vmid');
2791
9dbd1ee4
AD
2792 my $snapname = extract_param($param, 'snapname');
2793
d1914468
DM
2794 die "unable to use snapshot name 'current' (reserved name)\n"
2795 if $snapname eq 'current';
2796
7e7d7b61 2797 my $realcmd = sub {
22c377f0 2798 PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
75466c4f 2799 PVE::QemuServer::snapshot_create($vmid, $snapname, $param->{vmstate},
782f4f75 2800 $param->{freezefs}, $param->{description});
7e7d7b61
DM
2801 };
2802
2803 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
2804 }});
2805
154ccdcd
DM
2806__PACKAGE__->register_method({
2807 name => 'snapshot_cmd_idx',
2808 path => '{vmid}/snapshot/{snapname}',
2809 description => '',
2810 method => 'GET',
2811 permissions => {
2812 user => 'all',
2813 },
2814 parameters => {
2815 additionalProperties => 0,
2816 properties => {
2817 vmid => get_standard_option('pve-vmid'),
2818 node => get_standard_option('pve-node'),
8abd398b 2819 snapname => get_standard_option('pve-snapshot-name'),
154ccdcd
DM
2820 },
2821 },
2822 returns => {
2823 type => 'array',
2824 items => {
2825 type => "object",
2826 properties => {},
2827 },
2828 links => [ { rel => 'child', href => "{cmd}" } ],
2829 },
2830 code => sub {
2831 my ($param) = @_;
2832
2833 my $res = [];
2834
2835 push @$res, { cmd => 'rollback' };
d788cea6 2836 push @$res, { cmd => 'config' };
154ccdcd
DM
2837
2838 return $res;
2839 }});
2840
d788cea6
DM
2841__PACKAGE__->register_method({
2842 name => 'update_snapshot_config',
2843 path => '{vmid}/snapshot/{snapname}/config',
2844 method => 'PUT',
2845 protected => 1,
2846 proxyto => 'node',
2847 description => "Update snapshot metadata.",
2848 permissions => {
2849 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2850 },
2851 parameters => {
2852 additionalProperties => 0,
2853 properties => {
2854 node => get_standard_option('pve-node'),
2855 vmid => get_standard_option('pve-vmid'),
2856 snapname => get_standard_option('pve-snapshot-name'),
2857 description => {
2858 optional => 1,
2859 type => 'string',
2860 description => "A textual description or comment.",
2861 },
2862 },
2863 },
2864 returns => { type => 'null' },
2865 code => sub {
2866 my ($param) = @_;
2867
2868 my $rpcenv = PVE::RPCEnvironment::get();
2869
2870 my $authuser = $rpcenv->get_user();
2871
2872 my $vmid = extract_param($param, 'vmid');
2873
2874 my $snapname = extract_param($param, 'snapname');
2875
2876 return undef if !defined($param->{description});
2877
2878 my $updatefn = sub {
2879
2880 my $conf = PVE::QemuServer::load_config($vmid);
2881
2882 PVE::QemuServer::check_lock($conf);
2883
2884 my $snap = $conf->{snapshots}->{$snapname};
2885
75466c4f
DM
2886 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2887
d788cea6
DM
2888 $snap->{description} = $param->{description} if defined($param->{description});
2889
2890 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
2891 };
2892
2893 PVE::QemuServer::lock_config($vmid, $updatefn);
2894
2895 return undef;
2896 }});
2897
2898__PACKAGE__->register_method({
2899 name => 'get_snapshot_config',
2900 path => '{vmid}/snapshot/{snapname}/config',
2901 method => 'GET',
2902 proxyto => 'node',
2903 description => "Get snapshot configuration",
2904 permissions => {
2905 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
2906 },
2907 parameters => {
2908 additionalProperties => 0,
2909 properties => {
2910 node => get_standard_option('pve-node'),
2911 vmid => get_standard_option('pve-vmid'),
2912 snapname => get_standard_option('pve-snapshot-name'),
2913 },
2914 },
2915 returns => { type => "object" },
2916 code => sub {
2917 my ($param) = @_;
2918
2919 my $rpcenv = PVE::RPCEnvironment::get();
2920
2921 my $authuser = $rpcenv->get_user();
2922
2923 my $vmid = extract_param($param, 'vmid');
2924
2925 my $snapname = extract_param($param, 'snapname');
2926
2927 my $conf = PVE::QemuServer::load_config($vmid);
2928
2929 my $snap = $conf->{snapshots}->{$snapname};
2930
75466c4f
DM
2931 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2932
d788cea6
DM
2933 return $snap;
2934 }});
2935
7e7d7b61
DM
2936__PACKAGE__->register_method({
2937 name => 'rollback',
154ccdcd 2938 path => '{vmid}/snapshot/{snapname}/rollback',
7e7d7b61
DM
2939 method => 'POST',
2940 protected => 1,
2941 proxyto => 'node',
2942 description => "Rollback VM state to specified snapshot.",
2943 permissions => {
f1baf1df 2944 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2945 },
2946 parameters => {
2947 additionalProperties => 0,
2948 properties => {
2949 node => get_standard_option('pve-node'),
2950 vmid => get_standard_option('pve-vmid'),
8abd398b 2951 snapname => get_standard_option('pve-snapshot-name'),
7e7d7b61
DM
2952 },
2953 },
2954 returns => {
2955 type => 'string',
2956 description => "the task ID.",
2957 },
2958 code => sub {
2959 my ($param) = @_;
2960
2961 my $rpcenv = PVE::RPCEnvironment::get();
2962
2963 my $authuser = $rpcenv->get_user();
2964
2965 my $node = extract_param($param, 'node');
2966
2967 my $vmid = extract_param($param, 'vmid');
2968
2969 my $snapname = extract_param($param, 'snapname');
2970
7e7d7b61 2971 my $realcmd = sub {
22c377f0
DM
2972 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot VM $vmid: $snapname");
2973 PVE::QemuServer::snapshot_rollback($vmid, $snapname);
7e7d7b61
DM
2974 };
2975
2976 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $realcmd);
2977 }});
2978
2979__PACKAGE__->register_method({
2980 name => 'delsnapshot',
2981 path => '{vmid}/snapshot/{snapname}',
2982 method => 'DELETE',
2983 protected => 1,
2984 proxyto => 'node',
2985 description => "Delete a VM snapshot.",
2986 permissions => {
f1baf1df 2987 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
7e7d7b61
DM
2988 },
2989 parameters => {
2990 additionalProperties => 0,
2991 properties => {
2992 node => get_standard_option('pve-node'),
2993 vmid => get_standard_option('pve-vmid'),
8abd398b 2994 snapname => get_standard_option('pve-snapshot-name'),
3ee28e38
DM
2995 force => {
2996 optional => 1,
2997 type => 'boolean',
2998 description => "For removal from config file, even if removing disk snapshots fails.",
2999 },
7e7d7b61
DM
3000 },
3001 },
3002 returns => {
3003 type => 'string',
3004 description => "the task ID.",
3005 },
3006 code => sub {
3007 my ($param) = @_;
3008
3009 my $rpcenv = PVE::RPCEnvironment::get();
3010
3011 my $authuser = $rpcenv->get_user();
3012
3013 my $node = extract_param($param, 'node');
3014
3015 my $vmid = extract_param($param, 'vmid');
3016
3017 my $snapname = extract_param($param, 'snapname');
3018
7e7d7b61 3019 my $realcmd = sub {
22c377f0 3020 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
3ee28e38 3021 PVE::QemuServer::snapshot_delete($vmid, $snapname, $param->{force});
7e7d7b61 3022 };
9dbd1ee4 3023
7b2257a8 3024 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
9dbd1ee4
AD
3025 }});
3026
04a69bb4
AD
3027__PACKAGE__->register_method({
3028 name => 'template',
3029 path => '{vmid}/template',
3030 method => 'POST',
3031 protected => 1,
3032 proxyto => 'node',
3033 description => "Create a Template.",
b02691d8 3034 permissions => {
7af0a6c8
DM
3035 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
3036 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
b02691d8 3037 },
04a69bb4
AD
3038 parameters => {
3039 additionalProperties => 0,
3040 properties => {
3041 node => get_standard_option('pve-node'),
3042 vmid => get_standard_option('pve-vmid'),
3043 disk => {
3044 optional => 1,
3045 type => 'string',
3046 description => "If you want to convert only 1 disk to base image.",
3047 enum => [PVE::QemuServer::disknames()],
3048 },
3049
3050 },
3051 },
3052 returns => { type => 'null'},
3053 code => sub {
3054 my ($param) = @_;
3055
3056 my $rpcenv = PVE::RPCEnvironment::get();
3057
3058 my $authuser = $rpcenv->get_user();
3059
3060 my $node = extract_param($param, 'node');
3061
3062 my $vmid = extract_param($param, 'vmid');
3063
3064 my $disk = extract_param($param, 'disk');
3065
3066 my $updatefn = sub {
3067
3068 my $conf = PVE::QemuServer::load_config($vmid);
3069
3070 PVE::QemuServer::check_lock($conf);
3071
75466c4f 3072 die "unable to create template, because VM contains snapshots\n"
b91c2aae 3073 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
0402a80b 3074
75466c4f 3075 die "you can't convert a template to a template\n"
03c2d0ad 3076 if PVE::QemuServer::is_template($conf) && !$disk;
0402a80b 3077
75466c4f 3078 die "you can't convert a VM to template if VM is running\n"
218cab9a 3079 if PVE::QemuServer::check_running($vmid);
35c5fdef 3080
04a69bb4
AD
3081 my $realcmd = sub {
3082 PVE::QemuServer::template_create($vmid, $conf, $disk);
3083 };
04a69bb4 3084
75e7e997 3085 $conf->{template} = 1;
04a69bb4 3086 PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
75e7e997
DM
3087
3088 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
04a69bb4
AD
3089 };
3090
3091 PVE::QemuServer::lock_config($vmid, $updatefn);
3092 return undef;
3093 }});
3094
1e3baf05 30951;