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