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