]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
fix LXC backup after changing lxc to pct conf
[pve-container.git] / src / PVE / API2 / LXC.pm
1 package PVE::API2::LXC;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::Tools qw(extract_param run_command);
8 use PVE::Exception qw(raise raise_param_exc);
9 use PVE::INotify;
10 use PVE::Cluster qw(cfs_read_file);
11 use PVE::AccessControl;
12 use PVE::Storage;
13 use PVE::RESTHandler;
14 use PVE::RPCEnvironment;
15 use PVE::LXC;
16 use PVE::LXCCreate;
17 use PVE::HA::Config;
18 use PVE::JSONSchema qw(get_standard_option);
19 use base qw(PVE::RESTHandler);
20
21 use Data::Dumper; # fixme: remove
22
23 my $check_ct_modify_config_perm = sub {
24 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
25
26 return 1 if $authuser ne 'root@pam';
27
28 foreach my $opt (@$key_list) {
29
30 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
31 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
32 } elsif ($opt eq 'disk') {
33 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
34 } elsif ($opt eq 'memory' || $opt eq 'swap') {
35 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
36 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
37 $opt eq 'searchdomain' || $opt eq 'hostname') {
38 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
39 } else {
40 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
41 }
42 }
43
44 return 1;
45 };
46
47 my $alloc_rootfs = sub {
48 my ($storage_conf, $storage, $disk_size_gb, $vmid) = @_;
49
50 my $volid;
51
52 my $size = 4*1024*1024; # defaults to 4G
53
54 $size = int($disk_size_gb*1024) * 1024 if defined($disk_size_gb);
55
56 eval {
57 my $scfg = PVE::Storage::storage_config($storage_conf, $storage);
58 # fixme: use better naming ct-$vmid-disk-X.raw?
59
60 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
61 if ($size > 0) {
62 $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'raw',
63 "vm-$vmid-rootfs.raw", $size);
64 } else {
65 $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'subvol',
66 "subvol-$vmid-rootfs", 0);
67 }
68 } elsif ($scfg->{type} eq 'zfspool') {
69
70 $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'subvol',
71 "subvol-$vmid-rootfs", $size);
72 } elsif ($scfg->{type} eq 'drbd') {
73
74 $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'raw', undef, $size);
75
76 } else {
77 die "unable to create containers on storage type '$scfg->{type}'\n";
78 }
79 };
80 if (my $err = $@) {
81 # cleanup
82 eval { PVE::Storage::vdisk_free($storage_conf, $volid) if $volid; };
83 warn $@ if $@;
84 die $err;
85 }
86
87 return $volid;
88 };
89
90 PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
91 description => "The name of the snapshot.",
92 type => 'string', format => 'pve-configid',
93 maxLength => 40,
94 });
95
96 __PACKAGE__->register_method({
97 name => 'vmlist',
98 path => '',
99 method => 'GET',
100 description => "LXC container index (per node).",
101 permissions => {
102 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
103 user => 'all',
104 },
105 proxyto => 'node',
106 protected => 1, # /proc files are only readable by root
107 parameters => {
108 additionalProperties => 0,
109 properties => {
110 node => get_standard_option('pve-node'),
111 },
112 },
113 returns => {
114 type => 'array',
115 items => {
116 type => "object",
117 properties => {},
118 },
119 links => [ { rel => 'child', href => "{vmid}" } ],
120 },
121 code => sub {
122 my ($param) = @_;
123
124 my $rpcenv = PVE::RPCEnvironment::get();
125 my $authuser = $rpcenv->get_user();
126
127 my $vmstatus = PVE::LXC::vmstatus();
128
129 my $res = [];
130 foreach my $vmid (keys %$vmstatus) {
131 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
132
133 my $data = $vmstatus->{$vmid};
134 $data->{vmid} = $vmid;
135 push @$res, $data;
136 }
137
138 return $res;
139
140 }});
141
142 __PACKAGE__->register_method({
143 name => 'create_vm',
144 path => '',
145 method => 'POST',
146 description => "Create or restore a container.",
147 permissions => {
148 user => 'all', # check inside
149 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
150 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
151 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
152 },
153 protected => 1,
154 proxyto => 'node',
155 parameters => {
156 additionalProperties => 0,
157 properties => PVE::LXC::json_config_properties_no_rootfs({
158 node => get_standard_option('pve-node'),
159 vmid => get_standard_option('pve-vmid'),
160 ostemplate => {
161 description => "The OS template or backup file.",
162 type => 'string',
163 maxLength => 255,
164 },
165 password => {
166 optional => 1,
167 type => 'string',
168 description => "Sets root password inside container.",
169 minLength => 5,
170 },
171 storage => get_standard_option('pve-storage-id', {
172 description => "Target storage.",
173 default => 'local',
174 optional => 1,
175 }),
176 size => {
177 optional => 1,
178 type => 'number',
179 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
180 minimum => 0,
181 default => 4,
182 },
183 force => {
184 optional => 1,
185 type => 'boolean',
186 description => "Allow to overwrite existing container.",
187 },
188 restore => {
189 optional => 1,
190 type => 'boolean',
191 description => "Mark this as restore task.",
192 },
193 pool => {
194 optional => 1,
195 type => 'string', format => 'pve-poolid',
196 description => "Add the VM to the specified pool.",
197 },
198 }),
199 },
200 returns => {
201 type => 'string',
202 },
203 code => sub {
204 my ($param) = @_;
205
206 my $rpcenv = PVE::RPCEnvironment::get();
207
208 my $authuser = $rpcenv->get_user();
209
210 my $node = extract_param($param, 'node');
211
212 my $vmid = extract_param($param, 'vmid');
213
214 my $basecfg_fn = PVE::LXC::config_file($vmid);
215
216 my $same_container_exists = -f $basecfg_fn;
217
218 my $restore = extract_param($param, 'restore');
219
220 if ($restore) {
221 # fixme: limit allowed parameters
222
223 }
224
225 my $force = extract_param($param, 'force');
226
227 if (!($same_container_exists && $restore && $force)) {
228 PVE::Cluster::check_vmid_unused($vmid);
229 }
230
231 my $password = extract_param($param, 'password');
232
233 my $disksize = extract_param($param, 'size');
234
235 my $storage = extract_param($param, 'storage') // 'local';
236
237 my $storage_cfg = cfs_read_file("storage.cfg");
238
239 my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
240
241 raise_param_exc({ storage => "storage '$storage' does not support container root directories"})
242 if !($scfg->{content}->{images} || $scfg->{content}->{rootdir});
243
244 my $pool = extract_param($param, 'pool');
245
246 if (defined($pool)) {
247 $rpcenv->check_pool_exist($pool);
248 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
249 }
250
251 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
252
253 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
254 # OK
255 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
256 # OK
257 } elsif ($restore && $force && $same_container_exists &&
258 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
259 # OK: user has VM.Backup permissions, and want to restore an existing VM
260 } else {
261 raise_perm_exc();
262 }
263
264 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
265
266 PVE::Storage::activate_storage($storage_cfg, $storage);
267
268 my $ostemplate = extract_param($param, 'ostemplate');
269
270 my $archive;
271
272 if ($ostemplate eq '-') {
273 die "pipe requires cli environment\n"
274 if $rpcenv->{type} ne 'cli';
275 die "pipe can only be used with restore tasks\n"
276 if !$restore;
277 die "pipe requires --size parameter\n"
278 if !defined($disksize);
279 $archive = '-';
280 } else {
281 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
282 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
283 }
284
285 my $conf = {};
286
287 PVE::LXC::update_pct_config($vmid, $conf, 0, $param);
288
289 my $check_vmid_usage = sub {
290 if ($force) {
291 die "cant overwrite running container\n"
292 if PVE::LXC::check_running($vmid);
293 } else {
294 PVE::Cluster::check_vmid_unused($vmid);
295 }
296 };
297
298 my $code = sub {
299 &$check_vmid_usage(); # final check after locking
300
301 PVE::Cluster::check_cfs_quorum();
302
303 my $volid;
304
305 eval {
306 if (!defined($disksize)) {
307 if ($restore) {
308 (undef, $disksize) = PVE::LXCCreate::recover_config($archive);
309 die "unable to detect disk size - please specify with --size\n"
310 if !$disksize;
311 } else {
312 $disksize = 4;
313 }
314 }
315 $volid = &$alloc_rootfs($storage_cfg, $storage, $disksize, $vmid);
316
317 PVE::LXCCreate::create_rootfs($storage_cfg, $storage, $volid, $vmid, $conf,
318 $archive, $password, $restore);
319
320 $conf->{rootfs} = "$volid,size=$disksize";
321
322 # set some defaults
323 $conf->{hostname} ||= "CT$vmid";
324 $conf->{memory} ||= 512;
325 $conf->{swap} //= 512;
326
327 PVE::LXC::create_config($vmid, $conf);
328 };
329 if (my $err = $@) {
330 eval { PVE::Storage::vdisk_free($storage_cfg, $volid) if $volid; };
331 warn $@ if $@;
332 PVE::LXC::destroy_config($vmid);
333 die $err;
334 }
335 };
336
337 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
338
339 &$check_vmid_usage(); # first check before locking
340
341 return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate',
342 $vmid, $authuser, $realcmd);
343
344 }});
345
346 my $vm_config_perm_list = [
347 'VM.Config.Disk',
348 'VM.Config.CPU',
349 'VM.Config.Memory',
350 'VM.Config.Network',
351 'VM.Config.Options',
352 ];
353
354 __PACKAGE__->register_method({
355 name => 'update_vm',
356 path => '{vmid}/config',
357 method => 'PUT',
358 protected => 1,
359 proxyto => 'node',
360 description => "Set container options.",
361 permissions => {
362 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
363 },
364 parameters => {
365 additionalProperties => 0,
366 properties => PVE::LXC::json_config_properties(
367 {
368 node => get_standard_option('pve-node'),
369 vmid => get_standard_option('pve-vmid'),
370 delete => {
371 type => 'string', format => 'pve-configid-list',
372 description => "A list of settings you want to delete.",
373 optional => 1,
374 },
375 digest => {
376 type => 'string',
377 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
378 maxLength => 40,
379 optional => 1,
380 }
381 }),
382 },
383 returns => { type => 'null'},
384 code => sub {
385 my ($param) = @_;
386
387 my $rpcenv = PVE::RPCEnvironment::get();
388
389 my $authuser = $rpcenv->get_user();
390
391 my $node = extract_param($param, 'node');
392
393 my $vmid = extract_param($param, 'vmid');
394
395 my $digest = extract_param($param, 'digest');
396
397 die "no options specified\n" if !scalar(keys %$param);
398
399 my $delete_str = extract_param($param, 'delete');
400 my @delete = PVE::Tools::split_list($delete_str);
401
402 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
403
404 foreach my $opt (@delete) {
405 raise_param_exc({ delete => "you can't use '-$opt' and " .
406 "-delete $opt' at the same time" })
407 if defined($param->{$opt});
408
409 if (!PVE::LXC::option_exists($opt)) {
410 raise_param_exc({ delete => "unknown option '$opt'" });
411 }
412 }
413
414 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
415
416 my $storage_cfg = cfs_read_file("storage.cfg");
417
418 my $code = sub {
419
420 my $conf = PVE::LXC::load_config($vmid);
421 PVE::LXC::check_lock($conf);
422
423 PVE::Tools::assert_if_modified($digest, $conf->{digest});
424
425 my $running = PVE::LXC::check_running($vmid);
426
427 PVE::LXC::update_pct_config($vmid, $conf, $running, $param, \@delete);
428
429 PVE::LXC::write_config($vmid, $conf);
430 PVE::LXC::update_lxc_config($storage_cfg, $vmid, $conf);
431 };
432
433 PVE::LXC::lock_container($vmid, undef, $code);
434
435 return undef;
436 }});
437
438 __PACKAGE__->register_method ({
439 subclass => "PVE::API2::Firewall::CT",
440 path => '{vmid}/firewall',
441 });
442
443 __PACKAGE__->register_method({
444 name => 'vmdiridx',
445 path => '{vmid}',
446 method => 'GET',
447 proxyto => 'node',
448 description => "Directory index",
449 permissions => {
450 user => 'all',
451 },
452 parameters => {
453 additionalProperties => 0,
454 properties => {
455 node => get_standard_option('pve-node'),
456 vmid => get_standard_option('pve-vmid'),
457 },
458 },
459 returns => {
460 type => 'array',
461 items => {
462 type => "object",
463 properties => {
464 subdir => { type => 'string' },
465 },
466 },
467 links => [ { rel => 'child', href => "{subdir}" } ],
468 },
469 code => sub {
470 my ($param) = @_;
471
472 # test if VM exists
473 my $conf = PVE::LXC::load_config($param->{vmid});
474
475 my $res = [
476 { subdir => 'config' },
477 { subdir => 'status' },
478 { subdir => 'vncproxy' },
479 { subdir => 'vncwebsocket' },
480 { subdir => 'spiceproxy' },
481 { subdir => 'migrate' },
482 # { subdir => 'initlog' },
483 { subdir => 'rrd' },
484 { subdir => 'rrddata' },
485 { subdir => 'firewall' },
486 { subdir => 'snapshot' },
487 ];
488
489 return $res;
490 }});
491
492 __PACKAGE__->register_method({
493 name => 'rrd',
494 path => '{vmid}/rrd',
495 method => 'GET',
496 protected => 1, # fixme: can we avoid that?
497 permissions => {
498 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
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(
533 "pve2-vm/$param->{vmid}", $param->{timeframe},
534 $param->{ds}, $param->{cf});
535
536 }});
537
538 __PACKAGE__->register_method({
539 name => 'rrddata',
540 path => '{vmid}/rrddata',
541 method => 'GET',
542 protected => 1, # fixme: can we avoid that?
543 permissions => {
544 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
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({
581 name => 'vm_config',
582 path => '{vmid}/config',
583 method => 'GET',
584 proxyto => 'node',
585 description => "Get container configuration.",
586 permissions => {
587 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
588 },
589 parameters => {
590 additionalProperties => 0,
591 properties => {
592 node => get_standard_option('pve-node'),
593 vmid => get_standard_option('pve-vmid'),
594 },
595 },
596 returns => {
597 type => "object",
598 properties => {
599 digest => {
600 type => 'string',
601 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
602 }
603 },
604 },
605 code => sub {
606 my ($param) = @_;
607
608 my $conf = PVE::LXC::load_config($param->{vmid});
609
610 delete $conf->{snapshots};
611 delete $conf->{lxc};
612
613 return $conf;
614 }});
615
616 __PACKAGE__->register_method({
617 name => 'destroy_vm',
618 path => '{vmid}',
619 method => 'DELETE',
620 protected => 1,
621 proxyto => 'node',
622 description => "Destroy the container (also delete all uses files).",
623 permissions => {
624 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
625 },
626 parameters => {
627 additionalProperties => 0,
628 properties => {
629 node => get_standard_option('pve-node'),
630 vmid => get_standard_option('pve-vmid'),
631 },
632 },
633 returns => {
634 type => 'string',
635 },
636 code => sub {
637 my ($param) = @_;
638
639 my $rpcenv = PVE::RPCEnvironment::get();
640
641 my $authuser = $rpcenv->get_user();
642
643 my $vmid = $param->{vmid};
644
645 # test if container exists
646 my $conf = PVE::LXC::load_config($vmid);
647
648 my $storage_cfg = cfs_read_file("storage.cfg");
649
650 my $code = sub {
651 # reload config after lock
652 $conf = PVE::LXC::load_config($vmid);
653 PVE::LXC::check_lock($conf);
654
655 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
656 PVE::AccessControl::remove_vm_from_pool($vmid);
657 };
658
659 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
660
661 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
662 }});
663
664 my $sslcert;
665
666 __PACKAGE__->register_method ({
667 name => 'vncproxy',
668 path => '{vmid}/vncproxy',
669 method => 'POST',
670 protected => 1,
671 permissions => {
672 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
673 },
674 description => "Creates a TCP VNC proxy connections.",
675 parameters => {
676 additionalProperties => 0,
677 properties => {
678 node => get_standard_option('pve-node'),
679 vmid => get_standard_option('pve-vmid'),
680 websocket => {
681 optional => 1,
682 type => 'boolean',
683 description => "use websocket instead of standard VNC.",
684 },
685 },
686 },
687 returns => {
688 additionalProperties => 0,
689 properties => {
690 user => { type => 'string' },
691 ticket => { type => 'string' },
692 cert => { type => 'string' },
693 port => { type => 'integer' },
694 upid => { type => 'string' },
695 },
696 },
697 code => sub {
698 my ($param) = @_;
699
700 my $rpcenv = PVE::RPCEnvironment::get();
701
702 my $authuser = $rpcenv->get_user();
703
704 my $vmid = $param->{vmid};
705 my $node = $param->{node};
706
707 my $authpath = "/vms/$vmid";
708
709 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
710
711 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
712 if !$sslcert;
713
714 my ($remip, $family);
715
716 if ($node ne PVE::INotify::nodename()) {
717 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
718 } else {
719 $family = PVE::Tools::get_host_address_family($node);
720 }
721
722 my $port = PVE::Tools::next_vnc_port($family);
723
724 # NOTE: vncterm VNC traffic is already TLS encrypted,
725 # so we select the fastest chipher here (or 'none'?)
726 my $remcmd = $remip ?
727 ['/usr/bin/ssh', '-t', $remip] : [];
728
729 my $shcmd = [ '/usr/bin/dtach', '-A',
730 "/var/run/dtach/vzctlconsole$vmid",
731 '-r', 'winch', '-z',
732 'lxc-console', '-n', $vmid ];
733
734 my $realcmd = sub {
735 my $upid = shift;
736
737 syslog ('info', "starting lxc vnc proxy $upid\n");
738
739 my $timeout = 10;
740
741 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
742 '-timeout', $timeout, '-authpath', $authpath,
743 '-perm', 'VM.Console'];
744
745 if ($param->{websocket}) {
746 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
747 push @$cmd, '-notls', '-listen', 'localhost';
748 }
749
750 push @$cmd, '-c', @$remcmd, @$shcmd;
751
752 run_command($cmd);
753
754 return;
755 };
756
757 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
758
759 PVE::Tools::wait_for_vnc_port($port);
760
761 return {
762 user => $authuser,
763 ticket => $ticket,
764 port => $port,
765 upid => $upid,
766 cert => $sslcert,
767 };
768 }});
769
770 __PACKAGE__->register_method({
771 name => 'vncwebsocket',
772 path => '{vmid}/vncwebsocket',
773 method => 'GET',
774 permissions => {
775 description => "You also need to pass a valid ticket (vncticket).",
776 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
777 },
778 description => "Opens a weksocket for VNC traffic.",
779 parameters => {
780 additionalProperties => 0,
781 properties => {
782 node => get_standard_option('pve-node'),
783 vmid => get_standard_option('pve-vmid'),
784 vncticket => {
785 description => "Ticket from previous call to vncproxy.",
786 type => 'string',
787 maxLength => 512,
788 },
789 port => {
790 description => "Port number returned by previous vncproxy call.",
791 type => 'integer',
792 minimum => 5900,
793 maximum => 5999,
794 },
795 },
796 },
797 returns => {
798 type => "object",
799 properties => {
800 port => { type => 'string' },
801 },
802 },
803 code => sub {
804 my ($param) = @_;
805
806 my $rpcenv = PVE::RPCEnvironment::get();
807
808 my $authuser = $rpcenv->get_user();
809
810 my $authpath = "/vms/$param->{vmid}";
811
812 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
813
814 my $port = $param->{port};
815
816 return { port => $port };
817 }});
818
819 __PACKAGE__->register_method ({
820 name => 'spiceproxy',
821 path => '{vmid}/spiceproxy',
822 method => 'POST',
823 protected => 1,
824 proxyto => 'node',
825 permissions => {
826 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
827 },
828 description => "Returns a SPICE configuration to connect to the CT.",
829 parameters => {
830 additionalProperties => 0,
831 properties => {
832 node => get_standard_option('pve-node'),
833 vmid => get_standard_option('pve-vmid'),
834 proxy => get_standard_option('spice-proxy', { optional => 1 }),
835 },
836 },
837 returns => get_standard_option('remote-viewer-config'),
838 code => sub {
839 my ($param) = @_;
840
841 my $vmid = $param->{vmid};
842 my $node = $param->{node};
843 my $proxy = $param->{proxy};
844
845 my $authpath = "/vms/$vmid";
846 my $permissions = 'VM.Console';
847
848 my $shcmd = ['/usr/bin/dtach', '-A',
849 "/var/run/dtach/vzctlconsole$vmid",
850 '-r', 'winch', '-z',
851 'lxc-console', '-n', $vmid];
852
853 my $title = "CT $vmid";
854
855 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
856 }});
857
858 __PACKAGE__->register_method({
859 name => 'vmcmdidx',
860 path => '{vmid}/status',
861 method => 'GET',
862 proxyto => 'node',
863 description => "Directory index",
864 permissions => {
865 user => 'all',
866 },
867 parameters => {
868 additionalProperties => 0,
869 properties => {
870 node => get_standard_option('pve-node'),
871 vmid => get_standard_option('pve-vmid'),
872 },
873 },
874 returns => {
875 type => 'array',
876 items => {
877 type => "object",
878 properties => {
879 subdir => { type => 'string' },
880 },
881 },
882 links => [ { rel => 'child', href => "{subdir}" } ],
883 },
884 code => sub {
885 my ($param) = @_;
886
887 # test if VM exists
888 my $conf = PVE::LXC::load_config($param->{vmid});
889
890 my $res = [
891 { subdir => 'current' },
892 { subdir => 'start' },
893 { subdir => 'stop' },
894 { subdir => 'shutdown' },
895 { subdir => 'migrate' },
896 ];
897
898 return $res;
899 }});
900
901 __PACKAGE__->register_method({
902 name => 'vm_status',
903 path => '{vmid}/status/current',
904 method => 'GET',
905 proxyto => 'node',
906 protected => 1, # openvz /proc entries are only readable by root
907 description => "Get virtual machine status.",
908 permissions => {
909 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
910 },
911 parameters => {
912 additionalProperties => 0,
913 properties => {
914 node => get_standard_option('pve-node'),
915 vmid => get_standard_option('pve-vmid'),
916 },
917 },
918 returns => { type => 'object' },
919 code => sub {
920 my ($param) = @_;
921
922 # test if VM exists
923 my $conf = PVE::LXC::load_config($param->{vmid});
924
925 my $vmstatus = PVE::LXC::vmstatus($param->{vmid});
926 my $status = $vmstatus->{$param->{vmid}};
927
928 $status->{ha} = PVE::HA::Config::vm_is_ha_managed($param->{vmid}) ? 1 : 0;
929
930 return $status;
931 }});
932
933 __PACKAGE__->register_method({
934 name => 'vm_start',
935 path => '{vmid}/status/start',
936 method => 'POST',
937 protected => 1,
938 proxyto => 'node',
939 description => "Start the container.",
940 permissions => {
941 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
942 },
943 parameters => {
944 additionalProperties => 0,
945 properties => {
946 node => get_standard_option('pve-node'),
947 vmid => get_standard_option('pve-vmid'),
948 },
949 },
950 returns => {
951 type => 'string',
952 },
953 code => sub {
954 my ($param) = @_;
955
956 my $rpcenv = PVE::RPCEnvironment::get();
957
958 my $authuser = $rpcenv->get_user();
959
960 my $node = extract_param($param, 'node');
961
962 my $vmid = extract_param($param, 'vmid');
963
964 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
965
966 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
967
968 my $hacmd = sub {
969 my $upid = shift;
970
971 my $service = "ct:$vmid";
972
973 my $cmd = ['ha-manager', 'enable', $service];
974
975 print "Executing HA start for CT $vmid\n";
976
977 PVE::Tools::run_command($cmd);
978
979 return;
980 };
981
982 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
983
984 } else {
985
986 my $realcmd = sub {
987 my $upid = shift;
988
989 syslog('info', "starting CT $vmid: $upid\n");
990
991 my $conf = PVE::LXC::load_config($vmid);
992 my $storage_cfg = cfs_read_file("storage.cfg");
993
994 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
995 PVE::Storage::activate_volumes($storage_cfg, [$rootinfo->{volume}]);
996
997 PVE::LXC::update_lxc_config($storage_cfg, $vmid, $conf);
998
999 my $cmd = ['lxc-start', '-n', $vmid];
1000
1001 run_command($cmd);
1002
1003 return;
1004 };
1005
1006 return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
1007 }
1008 }});
1009
1010 __PACKAGE__->register_method({
1011 name => 'vm_stop',
1012 path => '{vmid}/status/stop',
1013 method => 'POST',
1014 protected => 1,
1015 proxyto => 'node',
1016 description => "Stop the container.",
1017 permissions => {
1018 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1019 },
1020 parameters => {
1021 additionalProperties => 0,
1022 properties => {
1023 node => get_standard_option('pve-node'),
1024 vmid => get_standard_option('pve-vmid'),
1025 },
1026 },
1027 returns => {
1028 type => 'string',
1029 },
1030 code => sub {
1031 my ($param) = @_;
1032
1033 my $rpcenv = PVE::RPCEnvironment::get();
1034
1035 my $authuser = $rpcenv->get_user();
1036
1037 my $node = extract_param($param, 'node');
1038
1039 my $vmid = extract_param($param, 'vmid');
1040
1041 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1042
1043 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1044
1045 my $hacmd = sub {
1046 my $upid = shift;
1047
1048 my $service = "ct:$vmid";
1049
1050 my $cmd = ['ha-manager', 'disable', $service];
1051
1052 print "Executing HA stop for CT $vmid\n";
1053
1054 PVE::Tools::run_command($cmd);
1055
1056 return;
1057 };
1058
1059 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1060
1061 } else {
1062
1063 my $realcmd = sub {
1064 my $upid = shift;
1065
1066 syslog('info', "stoping CT $vmid: $upid\n");
1067
1068 my $conf = PVE::LXC::load_config($vmid);
1069
1070 my $storage_cfg = PVE::Storage::config();
1071
1072 my $cmd = ['lxc-stop', '-n', $vmid, '--kill'];
1073
1074 run_command($cmd);
1075
1076 PVE::LXC::vm_stop_cleanup($storage_cfg, $vmid, $conf);
1077
1078 return;
1079 };
1080
1081 return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
1082 }
1083 }});
1084
1085 __PACKAGE__->register_method({
1086 name => 'vm_shutdown',
1087 path => '{vmid}/status/shutdown',
1088 method => 'POST',
1089 protected => 1,
1090 proxyto => 'node',
1091 description => "Shutdown the container.",
1092 permissions => {
1093 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1094 },
1095 parameters => {
1096 additionalProperties => 0,
1097 properties => {
1098 node => get_standard_option('pve-node'),
1099 vmid => get_standard_option('pve-vmid'),
1100 timeout => {
1101 description => "Wait maximal timeout seconds.",
1102 type => 'integer',
1103 minimum => 0,
1104 optional => 1,
1105 default => 60,
1106 },
1107 forceStop => {
1108 description => "Make sure the Container stops.",
1109 type => 'boolean',
1110 optional => 1,
1111 default => 0,
1112 }
1113 },
1114 },
1115 returns => {
1116 type => 'string',
1117 },
1118 code => sub {
1119 my ($param) = @_;
1120
1121 my $rpcenv = PVE::RPCEnvironment::get();
1122
1123 my $authuser = $rpcenv->get_user();
1124
1125 my $node = extract_param($param, 'node');
1126
1127 my $vmid = extract_param($param, 'vmid');
1128
1129 my $timeout = extract_param($param, 'timeout');
1130
1131 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1132
1133 my $realcmd = sub {
1134 my $upid = shift;
1135
1136 syslog('info', "shutdown CT $vmid: $upid\n");
1137
1138 my $cmd = ['lxc-stop', '-n', $vmid];
1139
1140 $timeout = 60 if !defined($timeout);
1141
1142 my $conf = PVE::LXC::load_config($vmid);
1143
1144 my $storage_cfg = PVE::Storage::config();
1145
1146 push @$cmd, '--timeout', $timeout;
1147
1148 eval { run_command($cmd, timeout => $timeout+5); };
1149 my $err = $@;
1150 if ($err && $param->{forceStop}) {
1151 $err = undef;
1152 warn "shutdown failed - forcing stop now\n";
1153
1154 push @$cmd, '--kill';
1155 run_command($cmd);
1156
1157 }
1158
1159 PVE::LXC::vm_stop_cleanup($storage_cfg, $vmid, $conf);
1160
1161 die $err if !$err;
1162
1163 return;
1164 };
1165
1166 my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
1167
1168 return $upid;
1169 }});
1170
1171 __PACKAGE__->register_method({
1172 name => 'vm_suspend',
1173 path => '{vmid}/status/suspend',
1174 method => 'POST',
1175 protected => 1,
1176 proxyto => 'node',
1177 description => "Suspend the container.",
1178 permissions => {
1179 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1180 },
1181 parameters => {
1182 additionalProperties => 0,
1183 properties => {
1184 node => get_standard_option('pve-node'),
1185 vmid => get_standard_option('pve-vmid'),
1186 },
1187 },
1188 returns => {
1189 type => 'string',
1190 },
1191 code => sub {
1192 my ($param) = @_;
1193
1194 my $rpcenv = PVE::RPCEnvironment::get();
1195
1196 my $authuser = $rpcenv->get_user();
1197
1198 my $node = extract_param($param, 'node');
1199
1200 my $vmid = extract_param($param, 'vmid');
1201
1202 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1203
1204 my $realcmd = sub {
1205 my $upid = shift;
1206
1207 syslog('info', "suspend CT $vmid: $upid\n");
1208
1209 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump'];
1210
1211 run_command($cmd);
1212
1213 return;
1214 };
1215
1216 my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
1217
1218 return $upid;
1219 }});
1220
1221 __PACKAGE__->register_method({
1222 name => 'vm_resume',
1223 path => '{vmid}/status/resume',
1224 method => 'POST',
1225 protected => 1,
1226 proxyto => 'node',
1227 description => "Resume the container.",
1228 permissions => {
1229 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1230 },
1231 parameters => {
1232 additionalProperties => 0,
1233 properties => {
1234 node => get_standard_option('pve-node'),
1235 vmid => get_standard_option('pve-vmid'),
1236 },
1237 },
1238 returns => {
1239 type => 'string',
1240 },
1241 code => sub {
1242 my ($param) = @_;
1243
1244 my $rpcenv = PVE::RPCEnvironment::get();
1245
1246 my $authuser = $rpcenv->get_user();
1247
1248 my $node = extract_param($param, 'node');
1249
1250 my $vmid = extract_param($param, 'vmid');
1251
1252 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
1253
1254 my $realcmd = sub {
1255 my $upid = shift;
1256
1257 syslog('info', "resume CT $vmid: $upid\n");
1258
1259 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground',
1260 '-D', '/var/liv/vz/dump'];
1261
1262 run_command($cmd);
1263
1264 return;
1265 };
1266
1267 my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
1268
1269 return $upid;
1270 }});
1271
1272 __PACKAGE__->register_method({
1273 name => 'migrate_vm',
1274 path => '{vmid}/migrate',
1275 method => 'POST',
1276 protected => 1,
1277 proxyto => 'node',
1278 description => "Migrate the container to another node. Creates a new migration task.",
1279 permissions => {
1280 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1281 },
1282 parameters => {
1283 additionalProperties => 0,
1284 properties => {
1285 node => get_standard_option('pve-node'),
1286 vmid => get_standard_option('pve-vmid'),
1287 target => get_standard_option('pve-node', { description => "Target node." }),
1288 online => {
1289 type => 'boolean',
1290 description => "Use online/live migration.",
1291 optional => 1,
1292 },
1293 },
1294 },
1295 returns => {
1296 type => 'string',
1297 description => "the task ID.",
1298 },
1299 code => sub {
1300 my ($param) = @_;
1301
1302 my $rpcenv = PVE::RPCEnvironment::get();
1303
1304 my $authuser = $rpcenv->get_user();
1305
1306 my $target = extract_param($param, 'target');
1307
1308 my $localnode = PVE::INotify::nodename();
1309 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1310
1311 PVE::Cluster::check_cfs_quorum();
1312
1313 PVE::Cluster::check_node_exists($target);
1314
1315 my $targetip = PVE::Cluster::remote_node_ip($target);
1316
1317 my $vmid = extract_param($param, 'vmid');
1318
1319 # test if VM exists
1320 PVE::LXC::load_config($vmid);
1321
1322 # try to detect errors early
1323 if (PVE::LXC::check_running($vmid)) {
1324 die "cant migrate running container without --online\n"
1325 if !$param->{online};
1326 }
1327
1328 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1329
1330 my $hacmd = sub {
1331 my $upid = shift;
1332
1333 my $service = "ct:$vmid";
1334
1335 my $cmd = ['ha-manager', 'migrate', $service, $target];
1336
1337 print "Executing HA migrate for CT $vmid to node $target\n";
1338
1339 PVE::Tools::run_command($cmd);
1340
1341 return;
1342 };
1343
1344 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1345
1346 } else {
1347
1348 my $realcmd = sub {
1349 my $upid = shift;
1350
1351 # fixme: implement lxc container migration
1352 die "lxc container migration not implemented\n";
1353
1354 return;
1355 };
1356
1357 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
1358 }
1359 }});
1360
1361 __PACKAGE__->register_method({
1362 name => 'snapshot',
1363 path => '{vmid}/snapshot',
1364 method => 'POST',
1365 protected => 1,
1366 proxyto => 'node',
1367 description => "Snapshot a container.",
1368 permissions => {
1369 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1370 },
1371 parameters => {
1372 additionalProperties => 0,
1373 properties => {
1374 node => get_standard_option('pve-node'),
1375 vmid => get_standard_option('pve-vmid'),
1376 snapname => get_standard_option('pve-lxc-snapshot-name'),
1377 # vmstate => {
1378 # optional => 1,
1379 # type => 'boolean',
1380 # description => "Save the vmstate",
1381 # },
1382 description => {
1383 optional => 1,
1384 type => 'string',
1385 description => "A textual description or comment.",
1386 },
1387 },
1388 },
1389 returns => {
1390 type => 'string',
1391 description => "the task ID.",
1392 },
1393 code => sub {
1394 my ($param) = @_;
1395
1396 my $rpcenv = PVE::RPCEnvironment::get();
1397
1398 my $authuser = $rpcenv->get_user();
1399
1400 my $node = extract_param($param, 'node');
1401
1402 my $vmid = extract_param($param, 'vmid');
1403
1404 my $snapname = extract_param($param, 'snapname');
1405
1406 die "unable to use snapshot name 'current' (reserved name)\n"
1407 if $snapname eq 'current';
1408
1409 my $realcmd = sub {
1410 PVE::Cluster::log_msg('info', $authuser, "snapshot container $vmid: $snapname");
1411 PVE::LXC::snapshot_create($vmid, $snapname, $param->{description});
1412 };
1413
1414 return $rpcenv->fork_worker('pctsnapshot', $vmid, $authuser, $realcmd);
1415 }});
1416
1417 __PACKAGE__->register_method({
1418 name => 'delsnapshot',
1419 path => '{vmid}/snapshot/{snapname}',
1420 method => 'DELETE',
1421 protected => 1,
1422 proxyto => 'node',
1423 description => "Delete a LXC snapshot.",
1424 permissions => {
1425 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1426 },
1427 parameters => {
1428 additionalProperties => 0,
1429 properties => {
1430 node => get_standard_option('pve-node'),
1431 vmid => get_standard_option('pve-vmid'),
1432 snapname => get_standard_option('pve-lxc-snapshot-name'),
1433 force => {
1434 optional => 1,
1435 type => 'boolean',
1436 description => "For removal from config file, even if removing disk snapshots fails.",
1437 },
1438 },
1439 },
1440 returns => {
1441 type => 'string',
1442 description => "the task ID.",
1443 },
1444 code => sub {
1445 my ($param) = @_;
1446
1447 my $rpcenv = PVE::RPCEnvironment::get();
1448
1449 my $authuser = $rpcenv->get_user();
1450
1451 my $node = extract_param($param, 'node');
1452
1453 my $vmid = extract_param($param, 'vmid');
1454
1455 my $snapname = extract_param($param, 'snapname');
1456
1457 my $realcmd = sub {
1458 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
1459 PVE::LXC::snapshot_delete($vmid, $snapname, $param->{force});
1460 };
1461
1462 return $rpcenv->fork_worker('lxcdelsnapshot', $vmid, $authuser, $realcmd);
1463 }});
1464
1465 __PACKAGE__->register_method({
1466 name => 'rollback',
1467 path => '{vmid}/snapshot/{snapname}/rollback',
1468 method => 'POST',
1469 protected => 1,
1470 proxyto => 'node',
1471 description => "Rollback LXC state to specified snapshot.",
1472 permissions => {
1473 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1474 },
1475 parameters => {
1476 additionalProperties => 0,
1477 properties => {
1478 node => get_standard_option('pve-node'),
1479 vmid => get_standard_option('pve-vmid'),
1480 snapname => get_standard_option('pve-lxc-snapshot-name'),
1481 },
1482 },
1483 returns => {
1484 type => 'string',
1485 description => "the task ID.",
1486 },
1487 code => sub {
1488 my ($param) = @_;
1489
1490 my $rpcenv = PVE::RPCEnvironment::get();
1491
1492 my $authuser = $rpcenv->get_user();
1493
1494 my $node = extract_param($param, 'node');
1495
1496 my $vmid = extract_param($param, 'vmid');
1497
1498 my $snapname = extract_param($param, 'snapname');
1499
1500 my $realcmd = sub {
1501 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot LXC $vmid: $snapname");
1502 PVE::LXC::snapshot_rollback($vmid, $snapname);
1503 };
1504
1505 return $rpcenv->fork_worker('lxcrollback', $vmid, $authuser, $realcmd);
1506 }});
1507
1508 __PACKAGE__->register_method({
1509 name => 'snapshot_list',
1510 path => '{vmid}/snapshot',
1511 method => 'GET',
1512 description => "List all snapshots.",
1513 permissions => {
1514 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1515 },
1516 proxyto => 'node',
1517 protected => 1, # lxc pid files are only readable by root
1518 parameters => {
1519 additionalProperties => 0,
1520 properties => {
1521 vmid => get_standard_option('pve-vmid'),
1522 node => get_standard_option('pve-node'),
1523 },
1524 },
1525 returns => {
1526 type => 'array',
1527 items => {
1528 type => "object",
1529 properties => {},
1530 },
1531 links => [ { rel => 'child', href => "{name}" } ],
1532 },
1533 code => sub {
1534 my ($param) = @_;
1535
1536 my $vmid = $param->{vmid};
1537
1538 my $conf = PVE::LXC::load_config($vmid);
1539 my $snaphash = $conf->{snapshots} || {};
1540
1541 my $res = [];
1542
1543 foreach my $name (keys %$snaphash) {
1544 my $d = $snaphash->{$name};
1545 my $item = {
1546 name => $name,
1547 snaptime => $d->{snaptime} || 0,
1548 description => $d->{description} || '',
1549 };
1550 $item->{parent} = $d->{parent} if defined($d->{parent});
1551 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
1552 push @$res, $item;
1553 }
1554
1555 my $running = PVE::LXC::check_running($vmid) ? 1 : 0;
1556 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
1557 $current->{parent} = $conf->{parent} if defined($conf->{parent});
1558
1559 push @$res, $current;
1560
1561 return $res;
1562 }});
1563
1564 __PACKAGE__->register_method({
1565 name => 'snapshot_cmd_idx',
1566 path => '{vmid}/snapshot/{snapname}',
1567 description => '',
1568 method => 'GET',
1569 permissions => {
1570 user => 'all',
1571 },
1572 parameters => {
1573 additionalProperties => 0,
1574 properties => {
1575 vmid => get_standard_option('pve-vmid'),
1576 node => get_standard_option('pve-node'),
1577 snapname => get_standard_option('pve-lxc-snapshot-name'),
1578 },
1579 },
1580 returns => {
1581 type => 'array',
1582 items => {
1583 type => "object",
1584 properties => {},
1585 },
1586 links => [ { rel => 'child', href => "{cmd}" } ],
1587 },
1588 code => sub {
1589 my ($param) = @_;
1590
1591 my $res = [];
1592
1593 push @$res, { cmd => 'rollback' };
1594 push @$res, { cmd => 'config' };
1595
1596 return $res;
1597 }});
1598
1599 __PACKAGE__->register_method({
1600 name => 'update_snapshot_config',
1601 path => '{vmid}/snapshot/{snapname}/config',
1602 method => 'PUT',
1603 protected => 1,
1604 proxyto => 'node',
1605 description => "Update snapshot metadata.",
1606 permissions => {
1607 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1608 },
1609 parameters => {
1610 additionalProperties => 0,
1611 properties => {
1612 node => get_standard_option('pve-node'),
1613 vmid => get_standard_option('pve-vmid'),
1614 snapname => get_standard_option('pve-lxc-snapshot-name'),
1615 description => {
1616 optional => 1,
1617 type => 'string',
1618 description => "A textual description or comment.",
1619 },
1620 },
1621 },
1622 returns => { type => 'null' },
1623 code => sub {
1624 my ($param) = @_;
1625
1626 my $rpcenv = PVE::RPCEnvironment::get();
1627
1628 my $authuser = $rpcenv->get_user();
1629
1630 my $vmid = extract_param($param, 'vmid');
1631
1632 my $snapname = extract_param($param, 'snapname');
1633
1634 return undef if !defined($param->{description});
1635
1636 my $updatefn = sub {
1637
1638 my $conf = PVE::LXC::load_config($vmid);
1639 PVE::LXC::check_lock($conf);
1640
1641 my $snap = $conf->{snapshots}->{$snapname};
1642
1643 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1644
1645 $snap->{description} = $param->{description} if defined($param->{description});
1646
1647 PVE::LXC::write_config($vmid, $conf, 1);
1648 };
1649
1650 PVE::LXC::lock_container($vmid, 10, $updatefn);
1651
1652 return undef;
1653 }});
1654
1655 __PACKAGE__->register_method({
1656 name => 'get_snapshot_config',
1657 path => '{vmid}/snapshot/{snapname}/config',
1658 method => 'GET',
1659 proxyto => 'node',
1660 description => "Get snapshot configuration",
1661 permissions => {
1662 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1663 },
1664 parameters => {
1665 additionalProperties => 0,
1666 properties => {
1667 node => get_standard_option('pve-node'),
1668 vmid => get_standard_option('pve-vmid'),
1669 snapname => get_standard_option('pve-lxc-snapshot-name'),
1670 },
1671 },
1672 returns => { type => "object" },
1673 code => sub {
1674 my ($param) = @_;
1675
1676 my $rpcenv = PVE::RPCEnvironment::get();
1677
1678 my $authuser = $rpcenv->get_user();
1679
1680 my $vmid = extract_param($param, 'vmid');
1681
1682 my $snapname = extract_param($param, 'snapname');
1683
1684 my $conf = PVE::LXC::load_config($vmid);
1685
1686 my $snap = $conf->{snapshots}->{$snapname};
1687
1688 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1689
1690 delete $snap->{lxc};
1691
1692 return $snap;
1693 }});
1694
1695 __PACKAGE__->register_method({
1696 name => 'vm_feature',
1697 path => '{vmid}/feature',
1698 method => 'GET',
1699 proxyto => 'node',
1700 protected => 1,
1701 description => "Check if feature for virtual machine is available.",
1702 permissions => {
1703 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1704 },
1705 parameters => {
1706 additionalProperties => 0,
1707 properties => {
1708 node => get_standard_option('pve-node'),
1709 vmid => get_standard_option('pve-vmid'),
1710 feature => {
1711 description => "Feature to check.",
1712 type => 'string',
1713 enum => [ 'snapshot' ],
1714 },
1715 snapname => get_standard_option('pve-lxc-snapshot-name', {
1716 optional => 1,
1717 }),
1718 },
1719 },
1720 returns => {
1721 type => "object",
1722 properties => {
1723 hasFeature => { type => 'boolean' },
1724 #nodes => {
1725 #type => 'array',
1726 #items => { type => 'string' },
1727 #}
1728 },
1729 },
1730 code => sub {
1731 my ($param) = @_;
1732
1733 my $node = extract_param($param, 'node');
1734
1735 my $vmid = extract_param($param, 'vmid');
1736
1737 my $snapname = extract_param($param, 'snapname');
1738
1739 my $feature = extract_param($param, 'feature');
1740
1741 my $conf = PVE::LXC::load_config($vmid);
1742
1743 if($snapname){
1744 my $snap = $conf->{snapshots}->{$snapname};
1745 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1746 $conf = $snap;
1747 }
1748 my $storage_cfg = PVE::Storage::config();
1749 #Maybe include later
1750 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1751 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
1752
1753 return {
1754 hasFeature => $hasFeature,
1755 #nodes => [ keys %$nodelist ],
1756 };
1757 }});
1758 1;