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