]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
481929501440e7f9d3edf01bed80369785dc93ee
[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 undef, $size);
64 } else {
65 $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'subvol',
66 undef, 0);
67 }
68 } elsif ($scfg->{type} eq 'zfspool') {
69
70 $volid = PVE::Storage::vdisk_alloc($storage_conf, $storage, $vmid, 'subvol',
71 undef, $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 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
336 };
337
338 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
339
340 &$check_vmid_usage(); # first check before locking
341
342 return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate',
343 $vmid, $authuser, $realcmd);
344
345 }});
346
347 my $vm_config_perm_list = [
348 'VM.Config.Disk',
349 'VM.Config.CPU',
350 'VM.Config.Memory',
351 'VM.Config.Network',
352 'VM.Config.Options',
353 ];
354
355 __PACKAGE__->register_method({
356 name => 'update_vm',
357 path => '{vmid}/config',
358 method => 'PUT',
359 protected => 1,
360 proxyto => 'node',
361 description => "Set container options.",
362 permissions => {
363 check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1],
364 },
365 parameters => {
366 additionalProperties => 0,
367 properties => PVE::LXC::json_config_properties(
368 {
369 node => get_standard_option('pve-node'),
370 vmid => get_standard_option('pve-vmid'),
371 delete => {
372 type => 'string', format => 'pve-configid-list',
373 description => "A list of settings you want to delete.",
374 optional => 1,
375 },
376 digest => {
377 type => 'string',
378 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
379 maxLength => 40,
380 optional => 1,
381 }
382 }),
383 },
384 returns => { type => 'null'},
385 code => sub {
386 my ($param) = @_;
387
388 my $rpcenv = PVE::RPCEnvironment::get();
389
390 my $authuser = $rpcenv->get_user();
391
392 my $node = extract_param($param, 'node');
393
394 my $vmid = extract_param($param, 'vmid');
395
396 my $digest = extract_param($param, 'digest');
397
398 die "no options specified\n" if !scalar(keys %$param);
399
400 my $delete_str = extract_param($param, 'delete');
401 my @delete = PVE::Tools::split_list($delete_str);
402
403 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
404
405 foreach my $opt (@delete) {
406 raise_param_exc({ delete => "you can't use '-$opt' and " .
407 "-delete $opt' at the same time" })
408 if defined($param->{$opt});
409
410 if (!PVE::LXC::option_exists($opt)) {
411 raise_param_exc({ delete => "unknown option '$opt'" });
412 }
413 }
414
415 &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
416
417 my $storage_cfg = cfs_read_file("storage.cfg");
418
419 my $code = sub {
420
421 my $conf = PVE::LXC::load_config($vmid);
422 PVE::LXC::check_lock($conf);
423
424 PVE::Tools::assert_if_modified($digest, $conf->{digest});
425
426 my $running = PVE::LXC::check_running($vmid);
427
428 PVE::LXC::update_pct_config($vmid, $conf, $running, $param, \@delete);
429
430 PVE::LXC::write_config($vmid, $conf);
431 PVE::LXC::update_lxc_config($storage_cfg, $vmid, $conf);
432 };
433
434 PVE::LXC::lock_container($vmid, undef, $code);
435
436 return undef;
437 }});
438
439 __PACKAGE__->register_method ({
440 subclass => "PVE::API2::Firewall::CT",
441 path => '{vmid}/firewall',
442 });
443
444 __PACKAGE__->register_method({
445 name => 'vmdiridx',
446 path => '{vmid}',
447 method => 'GET',
448 proxyto => 'node',
449 description => "Directory index",
450 permissions => {
451 user => 'all',
452 },
453 parameters => {
454 additionalProperties => 0,
455 properties => {
456 node => get_standard_option('pve-node'),
457 vmid => get_standard_option('pve-vmid'),
458 },
459 },
460 returns => {
461 type => 'array',
462 items => {
463 type => "object",
464 properties => {
465 subdir => { type => 'string' },
466 },
467 },
468 links => [ { rel => 'child', href => "{subdir}" } ],
469 },
470 code => sub {
471 my ($param) = @_;
472
473 # test if VM exists
474 my $conf = PVE::LXC::load_config($param->{vmid});
475
476 my $res = [
477 { subdir => 'config' },
478 { subdir => 'status' },
479 { subdir => 'vncproxy' },
480 { subdir => 'vncwebsocket' },
481 { subdir => 'spiceproxy' },
482 { subdir => 'migrate' },
483 # { subdir => 'initlog' },
484 { subdir => 'rrd' },
485 { subdir => 'rrddata' },
486 { subdir => 'firewall' },
487 { subdir => 'snapshot' },
488 ];
489
490 return $res;
491 }});
492
493 __PACKAGE__->register_method({
494 name => 'rrd',
495 path => '{vmid}/rrd',
496 method => 'GET',
497 protected => 1, # fixme: can we avoid that?
498 permissions => {
499 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
500 },
501 description => "Read VM RRD statistics (returns PNG)",
502 parameters => {
503 additionalProperties => 0,
504 properties => {
505 node => get_standard_option('pve-node'),
506 vmid => get_standard_option('pve-vmid'),
507 timeframe => {
508 description => "Specify the time frame you are interested in.",
509 type => 'string',
510 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
511 },
512 ds => {
513 description => "The list of datasources you want to display.",
514 type => 'string', format => 'pve-configid-list',
515 },
516 cf => {
517 description => "The RRD consolidation function",
518 type => 'string',
519 enum => [ 'AVERAGE', 'MAX' ],
520 optional => 1,
521 },
522 },
523 },
524 returns => {
525 type => "object",
526 properties => {
527 filename => { type => 'string' },
528 },
529 },
530 code => sub {
531 my ($param) = @_;
532
533 return PVE::Cluster::create_rrd_graph(
534 "pve2-vm/$param->{vmid}", $param->{timeframe},
535 $param->{ds}, $param->{cf});
536
537 }});
538
539 __PACKAGE__->register_method({
540 name => 'rrddata',
541 path => '{vmid}/rrddata',
542 method => 'GET',
543 protected => 1, # fixme: can we avoid that?
544 permissions => {
545 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
546 },
547 description => "Read VM RRD statistics",
548 parameters => {
549 additionalProperties => 0,
550 properties => {
551 node => get_standard_option('pve-node'),
552 vmid => get_standard_option('pve-vmid'),
553 timeframe => {
554 description => "Specify the time frame you are interested in.",
555 type => 'string',
556 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
557 },
558 cf => {
559 description => "The RRD consolidation function",
560 type => 'string',
561 enum => [ 'AVERAGE', 'MAX' ],
562 optional => 1,
563 },
564 },
565 },
566 returns => {
567 type => "array",
568 items => {
569 type => "object",
570 properties => {},
571 },
572 },
573 code => sub {
574 my ($param) = @_;
575
576 return PVE::Cluster::create_rrd_data(
577 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
578 }});
579
580
581 __PACKAGE__->register_method({
582 name => 'vm_config',
583 path => '{vmid}/config',
584 method => 'GET',
585 proxyto => 'node',
586 description => "Get container configuration.",
587 permissions => {
588 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
589 },
590 parameters => {
591 additionalProperties => 0,
592 properties => {
593 node => get_standard_option('pve-node'),
594 vmid => get_standard_option('pve-vmid'),
595 },
596 },
597 returns => {
598 type => "object",
599 properties => {
600 digest => {
601 type => 'string',
602 description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
603 }
604 },
605 },
606 code => sub {
607 my ($param) = @_;
608
609 my $conf = PVE::LXC::load_config($param->{vmid});
610
611 delete $conf->{snapshots};
612 delete $conf->{lxc};
613
614 return $conf;
615 }});
616
617 __PACKAGE__->register_method({
618 name => 'destroy_vm',
619 path => '{vmid}',
620 method => 'DELETE',
621 protected => 1,
622 proxyto => 'node',
623 description => "Destroy the container (also delete all uses files).",
624 permissions => {
625 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
626 },
627 parameters => {
628 additionalProperties => 0,
629 properties => {
630 node => get_standard_option('pve-node'),
631 vmid => get_standard_option('pve-vmid'),
632 },
633 },
634 returns => {
635 type => 'string',
636 },
637 code => sub {
638 my ($param) = @_;
639
640 my $rpcenv = PVE::RPCEnvironment::get();
641
642 my $authuser = $rpcenv->get_user();
643
644 my $vmid = $param->{vmid};
645
646 # test if container exists
647 my $conf = PVE::LXC::load_config($vmid);
648
649 my $storage_cfg = cfs_read_file("storage.cfg");
650
651 my $code = sub {
652 # reload config after lock
653 $conf = PVE::LXC::load_config($vmid);
654 PVE::LXC::check_lock($conf);
655
656 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
657 PVE::AccessControl::remove_vm_from_pool($vmid);
658 };
659
660 my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); };
661
662 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
663 }});
664
665 my $sslcert;
666
667 __PACKAGE__->register_method ({
668 name => 'vncproxy',
669 path => '{vmid}/vncproxy',
670 method => 'POST',
671 protected => 1,
672 permissions => {
673 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
674 },
675 description => "Creates a TCP VNC proxy connections.",
676 parameters => {
677 additionalProperties => 0,
678 properties => {
679 node => get_standard_option('pve-node'),
680 vmid => get_standard_option('pve-vmid'),
681 websocket => {
682 optional => 1,
683 type => 'boolean',
684 description => "use websocket instead of standard VNC.",
685 },
686 },
687 },
688 returns => {
689 additionalProperties => 0,
690 properties => {
691 user => { type => 'string' },
692 ticket => { type => 'string' },
693 cert => { type => 'string' },
694 port => { type => 'integer' },
695 upid => { type => 'string' },
696 },
697 },
698 code => sub {
699 my ($param) = @_;
700
701 my $rpcenv = PVE::RPCEnvironment::get();
702
703 my $authuser = $rpcenv->get_user();
704
705 my $vmid = $param->{vmid};
706 my $node = $param->{node};
707
708 my $authpath = "/vms/$vmid";
709
710 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
711
712 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
713 if !$sslcert;
714
715 my ($remip, $family);
716
717 if ($node ne PVE::INotify::nodename()) {
718 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
719 } else {
720 $family = PVE::Tools::get_host_address_family($node);
721 }
722
723 my $port = PVE::Tools::next_vnc_port($family);
724
725 # NOTE: vncterm VNC traffic is already TLS encrypted,
726 # so we select the fastest chipher here (or 'none'?)
727 my $remcmd = $remip ?
728 ['/usr/bin/ssh', '-t', $remip] : [];
729
730 my $shcmd = [ '/usr/bin/dtach', '-A',
731 "/var/run/dtach/vzctlconsole$vmid",
732 '-r', 'winch', '-z',
733 'lxc-console', '-n', $vmid ];
734
735 my $realcmd = sub {
736 my $upid = shift;
737
738 syslog ('info', "starting lxc vnc proxy $upid\n");
739
740 my $timeout = 10;
741
742 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
743 '-timeout', $timeout, '-authpath', $authpath,
744 '-perm', 'VM.Console'];
745
746 if ($param->{websocket}) {
747 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
748 push @$cmd, '-notls', '-listen', 'localhost';
749 }
750
751 push @$cmd, '-c', @$remcmd, @$shcmd;
752
753 run_command($cmd);
754
755 return;
756 };
757
758 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
759
760 PVE::Tools::wait_for_vnc_port($port);
761
762 return {
763 user => $authuser,
764 ticket => $ticket,
765 port => $port,
766 upid => $upid,
767 cert => $sslcert,
768 };
769 }});
770
771 __PACKAGE__->register_method({
772 name => 'vncwebsocket',
773 path => '{vmid}/vncwebsocket',
774 method => 'GET',
775 permissions => {
776 description => "You also need to pass a valid ticket (vncticket).",
777 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
778 },
779 description => "Opens a weksocket for VNC traffic.",
780 parameters => {
781 additionalProperties => 0,
782 properties => {
783 node => get_standard_option('pve-node'),
784 vmid => get_standard_option('pve-vmid'),
785 vncticket => {
786 description => "Ticket from previous call to vncproxy.",
787 type => 'string',
788 maxLength => 512,
789 },
790 port => {
791 description => "Port number returned by previous vncproxy call.",
792 type => 'integer',
793 minimum => 5900,
794 maximum => 5999,
795 },
796 },
797 },
798 returns => {
799 type => "object",
800 properties => {
801 port => { type => 'string' },
802 },
803 },
804 code => sub {
805 my ($param) = @_;
806
807 my $rpcenv = PVE::RPCEnvironment::get();
808
809 my $authuser = $rpcenv->get_user();
810
811 my $authpath = "/vms/$param->{vmid}";
812
813 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
814
815 my $port = $param->{port};
816
817 return { port => $port };
818 }});
819
820 __PACKAGE__->register_method ({
821 name => 'spiceproxy',
822 path => '{vmid}/spiceproxy',
823 method => 'POST',
824 protected => 1,
825 proxyto => 'node',
826 permissions => {
827 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
828 },
829 description => "Returns a SPICE configuration to connect to the CT.",
830 parameters => {
831 additionalProperties => 0,
832 properties => {
833 node => get_standard_option('pve-node'),
834 vmid => get_standard_option('pve-vmid'),
835 proxy => get_standard_option('spice-proxy', { optional => 1 }),
836 },
837 },
838 returns => get_standard_option('remote-viewer-config'),
839 code => sub {
840 my ($param) = @_;
841
842 my $vmid = $param->{vmid};
843 my $node = $param->{node};
844 my $proxy = $param->{proxy};
845
846 my $authpath = "/vms/$vmid";
847 my $permissions = 'VM.Console';
848
849 my $shcmd = ['/usr/bin/dtach', '-A',
850 "/var/run/dtach/vzctlconsole$vmid",
851 '-r', 'winch', '-z',
852 'lxc-console', '-n', $vmid];
853
854 my $title = "CT $vmid";
855
856 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
857 }});
858
859 __PACKAGE__->register_method({
860 name => 'vmcmdidx',
861 path => '{vmid}/status',
862 method => 'GET',
863 proxyto => 'node',
864 description => "Directory index",
865 permissions => {
866 user => 'all',
867 },
868 parameters => {
869 additionalProperties => 0,
870 properties => {
871 node => get_standard_option('pve-node'),
872 vmid => get_standard_option('pve-vmid'),
873 },
874 },
875 returns => {
876 type => 'array',
877 items => {
878 type => "object",
879 properties => {
880 subdir => { type => 'string' },
881 },
882 },
883 links => [ { rel => 'child', href => "{subdir}" } ],
884 },
885 code => sub {
886 my ($param) = @_;
887
888 # test if VM exists
889 my $conf = PVE::LXC::load_config($param->{vmid});
890
891 my $res = [
892 { subdir => 'current' },
893 { subdir => 'start' },
894 { subdir => 'stop' },
895 { subdir => 'shutdown' },
896 { subdir => 'migrate' },
897 ];
898
899 return $res;
900 }});
901
902 __PACKAGE__->register_method({
903 name => 'vm_status',
904 path => '{vmid}/status/current',
905 method => 'GET',
906 proxyto => 'node',
907 protected => 1, # openvz /proc entries are only readable by root
908 description => "Get virtual machine status.",
909 permissions => {
910 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
911 },
912 parameters => {
913 additionalProperties => 0,
914 properties => {
915 node => get_standard_option('pve-node'),
916 vmid => get_standard_option('pve-vmid'),
917 },
918 },
919 returns => { type => 'object' },
920 code => sub {
921 my ($param) = @_;
922
923 # test if VM exists
924 my $conf = PVE::LXC::load_config($param->{vmid});
925
926 my $vmstatus = PVE::LXC::vmstatus($param->{vmid});
927 my $status = $vmstatus->{$param->{vmid}};
928
929 $status->{ha} = PVE::HA::Config::vm_is_ha_managed($param->{vmid}) ? 1 : 0;
930
931 return $status;
932 }});
933
934 __PACKAGE__->register_method({
935 name => 'vm_start',
936 path => '{vmid}/status/start',
937 method => 'POST',
938 protected => 1,
939 proxyto => 'node',
940 description => "Start the container.",
941 permissions => {
942 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
943 },
944 parameters => {
945 additionalProperties => 0,
946 properties => {
947 node => get_standard_option('pve-node'),
948 vmid => get_standard_option('pve-vmid'),
949 },
950 },
951 returns => {
952 type => 'string',
953 },
954 code => sub {
955 my ($param) = @_;
956
957 my $rpcenv = PVE::RPCEnvironment::get();
958
959 my $authuser = $rpcenv->get_user();
960
961 my $node = extract_param($param, 'node');
962
963 my $vmid = extract_param($param, 'vmid');
964
965 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
966
967 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
968
969 my $hacmd = sub {
970 my $upid = shift;
971
972 my $service = "ct:$vmid";
973
974 my $cmd = ['ha-manager', 'enable', $service];
975
976 print "Executing HA start for CT $vmid\n";
977
978 PVE::Tools::run_command($cmd);
979
980 return;
981 };
982
983 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
984
985 } else {
986
987 my $realcmd = sub {
988 my $upid = shift;
989
990 syslog('info', "starting CT $vmid: $upid\n");
991
992 my $conf = PVE::LXC::load_config($vmid);
993 my $storage_cfg = cfs_read_file("storage.cfg");
994
995 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
996 PVE::Storage::activate_volumes($storage_cfg, [$rootinfo->{volume}]);
997
998 PVE::LXC::update_lxc_config($storage_cfg, $vmid, $conf);
999
1000 my $cmd = ['lxc-start', '-n', $vmid];
1001
1002 run_command($cmd);
1003
1004 return;
1005 };
1006
1007 return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
1008 }
1009 }});
1010
1011 __PACKAGE__->register_method({
1012 name => 'vm_stop',
1013 path => '{vmid}/status/stop',
1014 method => 'POST',
1015 protected => 1,
1016 proxyto => 'node',
1017 description => "Stop the container.",
1018 permissions => {
1019 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1020 },
1021 parameters => {
1022 additionalProperties => 0,
1023 properties => {
1024 node => get_standard_option('pve-node'),
1025 vmid => get_standard_option('pve-vmid'),
1026 },
1027 },
1028 returns => {
1029 type => 'string',
1030 },
1031 code => sub {
1032 my ($param) = @_;
1033
1034 my $rpcenv = PVE::RPCEnvironment::get();
1035
1036 my $authuser = $rpcenv->get_user();
1037
1038 my $node = extract_param($param, 'node');
1039
1040 my $vmid = extract_param($param, 'vmid');
1041
1042 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1043
1044 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1045
1046 my $hacmd = sub {
1047 my $upid = shift;
1048
1049 my $service = "ct:$vmid";
1050
1051 my $cmd = ['ha-manager', 'disable', $service];
1052
1053 print "Executing HA stop for CT $vmid\n";
1054
1055 PVE::Tools::run_command($cmd);
1056
1057 return;
1058 };
1059
1060 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1061
1062 } else {
1063
1064 my $realcmd = sub {
1065 my $upid = shift;
1066
1067 syslog('info', "stoping CT $vmid: $upid\n");
1068
1069 my $conf = PVE::LXC::load_config($vmid);
1070
1071 my $storage_cfg = PVE::Storage::config();
1072
1073 my $cmd = ['lxc-stop', '-n', $vmid, '--kill'];
1074
1075 run_command($cmd);
1076
1077 PVE::LXC::vm_stop_cleanup($storage_cfg, $vmid, $conf);
1078
1079 return;
1080 };
1081
1082 return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
1083 }
1084 }});
1085
1086 __PACKAGE__->register_method({
1087 name => 'vm_shutdown',
1088 path => '{vmid}/status/shutdown',
1089 method => 'POST',
1090 protected => 1,
1091 proxyto => 'node',
1092 description => "Shutdown the container.",
1093 permissions => {
1094 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1095 },
1096 parameters => {
1097 additionalProperties => 0,
1098 properties => {
1099 node => get_standard_option('pve-node'),
1100 vmid => get_standard_option('pve-vmid'),
1101 timeout => {
1102 description => "Wait maximal timeout seconds.",
1103 type => 'integer',
1104 minimum => 0,
1105 optional => 1,
1106 default => 60,
1107 },
1108 forceStop => {
1109 description => "Make sure the Container stops.",
1110 type => 'boolean',
1111 optional => 1,
1112 default => 0,
1113 }
1114 },
1115 },
1116 returns => {
1117 type => 'string',
1118 },
1119 code => sub {
1120 my ($param) = @_;
1121
1122 my $rpcenv = PVE::RPCEnvironment::get();
1123
1124 my $authuser = $rpcenv->get_user();
1125
1126 my $node = extract_param($param, 'node');
1127
1128 my $vmid = extract_param($param, 'vmid');
1129
1130 my $timeout = extract_param($param, 'timeout');
1131
1132 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1133
1134 my $realcmd = sub {
1135 my $upid = shift;
1136
1137 syslog('info', "shutdown CT $vmid: $upid\n");
1138
1139 my $cmd = ['lxc-stop', '-n', $vmid];
1140
1141 $timeout = 60 if !defined($timeout);
1142
1143 my $conf = PVE::LXC::load_config($vmid);
1144
1145 my $storage_cfg = PVE::Storage::config();
1146
1147 push @$cmd, '--timeout', $timeout;
1148
1149 eval { run_command($cmd, timeout => $timeout+5); };
1150 my $err = $@;
1151 if ($err && $param->{forceStop}) {
1152 $err = undef;
1153 warn "shutdown failed - forcing stop now\n";
1154
1155 push @$cmd, '--kill';
1156 run_command($cmd);
1157
1158 }
1159
1160 PVE::LXC::vm_stop_cleanup($storage_cfg, $vmid, $conf);
1161
1162 die $err if !$err;
1163
1164 return;
1165 };
1166
1167 my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
1168
1169 return $upid;
1170 }});
1171
1172 __PACKAGE__->register_method({
1173 name => 'vm_suspend',
1174 path => '{vmid}/status/suspend',
1175 method => 'POST',
1176 protected => 1,
1177 proxyto => 'node',
1178 description => "Suspend the container.",
1179 permissions => {
1180 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1181 },
1182 parameters => {
1183 additionalProperties => 0,
1184 properties => {
1185 node => get_standard_option('pve-node'),
1186 vmid => get_standard_option('pve-vmid'),
1187 },
1188 },
1189 returns => {
1190 type => 'string',
1191 },
1192 code => sub {
1193 my ($param) = @_;
1194
1195 my $rpcenv = PVE::RPCEnvironment::get();
1196
1197 my $authuser = $rpcenv->get_user();
1198
1199 my $node = extract_param($param, 'node');
1200
1201 my $vmid = extract_param($param, 'vmid');
1202
1203 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1204
1205 my $realcmd = sub {
1206 my $upid = shift;
1207
1208 syslog('info', "suspend CT $vmid: $upid\n");
1209
1210 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump'];
1211
1212 run_command($cmd);
1213
1214 return;
1215 };
1216
1217 my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
1218
1219 return $upid;
1220 }});
1221
1222 __PACKAGE__->register_method({
1223 name => 'vm_resume',
1224 path => '{vmid}/status/resume',
1225 method => 'POST',
1226 protected => 1,
1227 proxyto => 'node',
1228 description => "Resume the container.",
1229 permissions => {
1230 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1231 },
1232 parameters => {
1233 additionalProperties => 0,
1234 properties => {
1235 node => get_standard_option('pve-node'),
1236 vmid => get_standard_option('pve-vmid'),
1237 },
1238 },
1239 returns => {
1240 type => 'string',
1241 },
1242 code => sub {
1243 my ($param) = @_;
1244
1245 my $rpcenv = PVE::RPCEnvironment::get();
1246
1247 my $authuser = $rpcenv->get_user();
1248
1249 my $node = extract_param($param, 'node');
1250
1251 my $vmid = extract_param($param, 'vmid');
1252
1253 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
1254
1255 my $realcmd = sub {
1256 my $upid = shift;
1257
1258 syslog('info', "resume CT $vmid: $upid\n");
1259
1260 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground',
1261 '-D', '/var/liv/vz/dump'];
1262
1263 run_command($cmd);
1264
1265 return;
1266 };
1267
1268 my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
1269
1270 return $upid;
1271 }});
1272
1273 __PACKAGE__->register_method({
1274 name => 'migrate_vm',
1275 path => '{vmid}/migrate',
1276 method => 'POST',
1277 protected => 1,
1278 proxyto => 'node',
1279 description => "Migrate the container to another node. Creates a new migration task.",
1280 permissions => {
1281 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1282 },
1283 parameters => {
1284 additionalProperties => 0,
1285 properties => {
1286 node => get_standard_option('pve-node'),
1287 vmid => get_standard_option('pve-vmid'),
1288 target => get_standard_option('pve-node', { description => "Target node." }),
1289 online => {
1290 type => 'boolean',
1291 description => "Use online/live migration.",
1292 optional => 1,
1293 },
1294 },
1295 },
1296 returns => {
1297 type => 'string',
1298 description => "the task ID.",
1299 },
1300 code => sub {
1301 my ($param) = @_;
1302
1303 my $rpcenv = PVE::RPCEnvironment::get();
1304
1305 my $authuser = $rpcenv->get_user();
1306
1307 my $target = extract_param($param, 'target');
1308
1309 my $localnode = PVE::INotify::nodename();
1310 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1311
1312 PVE::Cluster::check_cfs_quorum();
1313
1314 PVE::Cluster::check_node_exists($target);
1315
1316 my $targetip = PVE::Cluster::remote_node_ip($target);
1317
1318 my $vmid = extract_param($param, 'vmid');
1319
1320 # test if VM exists
1321 PVE::LXC::load_config($vmid);
1322
1323 # try to detect errors early
1324 if (PVE::LXC::check_running($vmid)) {
1325 die "cant migrate running container without --online\n"
1326 if !$param->{online};
1327 }
1328
1329 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1330
1331 my $hacmd = sub {
1332 my $upid = shift;
1333
1334 my $service = "ct:$vmid";
1335
1336 my $cmd = ['ha-manager', 'migrate', $service, $target];
1337
1338 print "Executing HA migrate for CT $vmid to node $target\n";
1339
1340 PVE::Tools::run_command($cmd);
1341
1342 return;
1343 };
1344
1345 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1346
1347 } else {
1348
1349 my $realcmd = sub {
1350 my $upid = shift;
1351
1352 # fixme: implement lxc container migration
1353 die "lxc container migration not implemented\n";
1354
1355 return;
1356 };
1357
1358 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
1359 }
1360 }});
1361
1362 __PACKAGE__->register_method({
1363 name => 'snapshot',
1364 path => '{vmid}/snapshot',
1365 method => 'POST',
1366 protected => 1,
1367 proxyto => 'node',
1368 description => "Snapshot a container.",
1369 permissions => {
1370 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1371 },
1372 parameters => {
1373 additionalProperties => 0,
1374 properties => {
1375 node => get_standard_option('pve-node'),
1376 vmid => get_standard_option('pve-vmid'),
1377 snapname => get_standard_option('pve-lxc-snapshot-name'),
1378 # vmstate => {
1379 # optional => 1,
1380 # type => 'boolean',
1381 # description => "Save the vmstate",
1382 # },
1383 description => {
1384 optional => 1,
1385 type => 'string',
1386 description => "A textual description or comment.",
1387 },
1388 },
1389 },
1390 returns => {
1391 type => 'string',
1392 description => "the task ID.",
1393 },
1394 code => sub {
1395 my ($param) = @_;
1396
1397 my $rpcenv = PVE::RPCEnvironment::get();
1398
1399 my $authuser = $rpcenv->get_user();
1400
1401 my $node = extract_param($param, 'node');
1402
1403 my $vmid = extract_param($param, 'vmid');
1404
1405 my $snapname = extract_param($param, 'snapname');
1406
1407 die "unable to use snapshot name 'current' (reserved name)\n"
1408 if $snapname eq 'current';
1409
1410 my $realcmd = sub {
1411 PVE::Cluster::log_msg('info', $authuser, "snapshot container $vmid: $snapname");
1412 PVE::LXC::snapshot_create($vmid, $snapname, $param->{description});
1413 };
1414
1415 return $rpcenv->fork_worker('pctsnapshot', $vmid, $authuser, $realcmd);
1416 }});
1417
1418 __PACKAGE__->register_method({
1419 name => 'delsnapshot',
1420 path => '{vmid}/snapshot/{snapname}',
1421 method => 'DELETE',
1422 protected => 1,
1423 proxyto => 'node',
1424 description => "Delete a LXC snapshot.",
1425 permissions => {
1426 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1427 },
1428 parameters => {
1429 additionalProperties => 0,
1430 properties => {
1431 node => get_standard_option('pve-node'),
1432 vmid => get_standard_option('pve-vmid'),
1433 snapname => get_standard_option('pve-lxc-snapshot-name'),
1434 force => {
1435 optional => 1,
1436 type => 'boolean',
1437 description => "For removal from config file, even if removing disk snapshots fails.",
1438 },
1439 },
1440 },
1441 returns => {
1442 type => 'string',
1443 description => "the task ID.",
1444 },
1445 code => sub {
1446 my ($param) = @_;
1447
1448 my $rpcenv = PVE::RPCEnvironment::get();
1449
1450 my $authuser = $rpcenv->get_user();
1451
1452 my $node = extract_param($param, 'node');
1453
1454 my $vmid = extract_param($param, 'vmid');
1455
1456 my $snapname = extract_param($param, 'snapname');
1457
1458 my $realcmd = sub {
1459 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
1460 PVE::LXC::snapshot_delete($vmid, $snapname, $param->{force});
1461 };
1462
1463 return $rpcenv->fork_worker('lxcdelsnapshot', $vmid, $authuser, $realcmd);
1464 }});
1465
1466 __PACKAGE__->register_method({
1467 name => 'rollback',
1468 path => '{vmid}/snapshot/{snapname}/rollback',
1469 method => 'POST',
1470 protected => 1,
1471 proxyto => 'node',
1472 description => "Rollback LXC state to specified snapshot.",
1473 permissions => {
1474 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1475 },
1476 parameters => {
1477 additionalProperties => 0,
1478 properties => {
1479 node => get_standard_option('pve-node'),
1480 vmid => get_standard_option('pve-vmid'),
1481 snapname => get_standard_option('pve-lxc-snapshot-name'),
1482 },
1483 },
1484 returns => {
1485 type => 'string',
1486 description => "the task ID.",
1487 },
1488 code => sub {
1489 my ($param) = @_;
1490
1491 my $rpcenv = PVE::RPCEnvironment::get();
1492
1493 my $authuser = $rpcenv->get_user();
1494
1495 my $node = extract_param($param, 'node');
1496
1497 my $vmid = extract_param($param, 'vmid');
1498
1499 my $snapname = extract_param($param, 'snapname');
1500
1501 my $realcmd = sub {
1502 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot LXC $vmid: $snapname");
1503 PVE::LXC::snapshot_rollback($vmid, $snapname);
1504 };
1505
1506 return $rpcenv->fork_worker('lxcrollback', $vmid, $authuser, $realcmd);
1507 }});
1508
1509 __PACKAGE__->register_method({
1510 name => 'snapshot_list',
1511 path => '{vmid}/snapshot',
1512 method => 'GET',
1513 description => "List all snapshots.",
1514 permissions => {
1515 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1516 },
1517 proxyto => 'node',
1518 protected => 1, # lxc pid files are only readable by root
1519 parameters => {
1520 additionalProperties => 0,
1521 properties => {
1522 vmid => get_standard_option('pve-vmid'),
1523 node => get_standard_option('pve-node'),
1524 },
1525 },
1526 returns => {
1527 type => 'array',
1528 items => {
1529 type => "object",
1530 properties => {},
1531 },
1532 links => [ { rel => 'child', href => "{name}" } ],
1533 },
1534 code => sub {
1535 my ($param) = @_;
1536
1537 my $vmid = $param->{vmid};
1538
1539 my $conf = PVE::LXC::load_config($vmid);
1540 my $snaphash = $conf->{snapshots} || {};
1541
1542 my $res = [];
1543
1544 foreach my $name (keys %$snaphash) {
1545 my $d = $snaphash->{$name};
1546 my $item = {
1547 name => $name,
1548 snaptime => $d->{snaptime} || 0,
1549 description => $d->{description} || '',
1550 };
1551 $item->{parent} = $d->{parent} if defined($d->{parent});
1552 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
1553 push @$res, $item;
1554 }
1555
1556 my $running = PVE::LXC::check_running($vmid) ? 1 : 0;
1557 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
1558 $current->{parent} = $conf->{parent} if defined($conf->{parent});
1559
1560 push @$res, $current;
1561
1562 return $res;
1563 }});
1564
1565 __PACKAGE__->register_method({
1566 name => 'snapshot_cmd_idx',
1567 path => '{vmid}/snapshot/{snapname}',
1568 description => '',
1569 method => 'GET',
1570 permissions => {
1571 user => 'all',
1572 },
1573 parameters => {
1574 additionalProperties => 0,
1575 properties => {
1576 vmid => get_standard_option('pve-vmid'),
1577 node => get_standard_option('pve-node'),
1578 snapname => get_standard_option('pve-lxc-snapshot-name'),
1579 },
1580 },
1581 returns => {
1582 type => 'array',
1583 items => {
1584 type => "object",
1585 properties => {},
1586 },
1587 links => [ { rel => 'child', href => "{cmd}" } ],
1588 },
1589 code => sub {
1590 my ($param) = @_;
1591
1592 my $res = [];
1593
1594 push @$res, { cmd => 'rollback' };
1595 push @$res, { cmd => 'config' };
1596
1597 return $res;
1598 }});
1599
1600 __PACKAGE__->register_method({
1601 name => 'update_snapshot_config',
1602 path => '{vmid}/snapshot/{snapname}/config',
1603 method => 'PUT',
1604 protected => 1,
1605 proxyto => 'node',
1606 description => "Update snapshot metadata.",
1607 permissions => {
1608 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1609 },
1610 parameters => {
1611 additionalProperties => 0,
1612 properties => {
1613 node => get_standard_option('pve-node'),
1614 vmid => get_standard_option('pve-vmid'),
1615 snapname => get_standard_option('pve-lxc-snapshot-name'),
1616 description => {
1617 optional => 1,
1618 type => 'string',
1619 description => "A textual description or comment.",
1620 },
1621 },
1622 },
1623 returns => { type => 'null' },
1624 code => sub {
1625 my ($param) = @_;
1626
1627 my $rpcenv = PVE::RPCEnvironment::get();
1628
1629 my $authuser = $rpcenv->get_user();
1630
1631 my $vmid = extract_param($param, 'vmid');
1632
1633 my $snapname = extract_param($param, 'snapname');
1634
1635 return undef if !defined($param->{description});
1636
1637 my $updatefn = sub {
1638
1639 my $conf = PVE::LXC::load_config($vmid);
1640 PVE::LXC::check_lock($conf);
1641
1642 my $snap = $conf->{snapshots}->{$snapname};
1643
1644 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1645
1646 $snap->{description} = $param->{description} if defined($param->{description});
1647
1648 PVE::LXC::write_config($vmid, $conf, 1);
1649 };
1650
1651 PVE::LXC::lock_container($vmid, 10, $updatefn);
1652
1653 return undef;
1654 }});
1655
1656 __PACKAGE__->register_method({
1657 name => 'get_snapshot_config',
1658 path => '{vmid}/snapshot/{snapname}/config',
1659 method => 'GET',
1660 proxyto => 'node',
1661 description => "Get snapshot configuration",
1662 permissions => {
1663 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1664 },
1665 parameters => {
1666 additionalProperties => 0,
1667 properties => {
1668 node => get_standard_option('pve-node'),
1669 vmid => get_standard_option('pve-vmid'),
1670 snapname => get_standard_option('pve-lxc-snapshot-name'),
1671 },
1672 },
1673 returns => { type => "object" },
1674 code => sub {
1675 my ($param) = @_;
1676
1677 my $rpcenv = PVE::RPCEnvironment::get();
1678
1679 my $authuser = $rpcenv->get_user();
1680
1681 my $vmid = extract_param($param, 'vmid');
1682
1683 my $snapname = extract_param($param, 'snapname');
1684
1685 my $conf = PVE::LXC::load_config($vmid);
1686
1687 my $snap = $conf->{snapshots}->{$snapname};
1688
1689 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1690
1691 delete $snap->{lxc};
1692
1693 return $snap;
1694 }});
1695
1696 __PACKAGE__->register_method({
1697 name => 'vm_feature',
1698 path => '{vmid}/feature',
1699 method => 'GET',
1700 proxyto => 'node',
1701 protected => 1,
1702 description => "Check if feature for virtual machine is available.",
1703 permissions => {
1704 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1705 },
1706 parameters => {
1707 additionalProperties => 0,
1708 properties => {
1709 node => get_standard_option('pve-node'),
1710 vmid => get_standard_option('pve-vmid'),
1711 feature => {
1712 description => "Feature to check.",
1713 type => 'string',
1714 enum => [ 'snapshot' ],
1715 },
1716 snapname => get_standard_option('pve-lxc-snapshot-name', {
1717 optional => 1,
1718 }),
1719 },
1720 },
1721 returns => {
1722 type => "object",
1723 properties => {
1724 hasFeature => { type => 'boolean' },
1725 #nodes => {
1726 #type => 'array',
1727 #items => { type => 'string' },
1728 #}
1729 },
1730 },
1731 code => sub {
1732 my ($param) = @_;
1733
1734 my $node = extract_param($param, 'node');
1735
1736 my $vmid = extract_param($param, 'vmid');
1737
1738 my $snapname = extract_param($param, 'snapname');
1739
1740 my $feature = extract_param($param, 'feature');
1741
1742 my $conf = PVE::LXC::load_config($vmid);
1743
1744 if($snapname){
1745 my $snap = $conf->{snapshots}->{$snapname};
1746 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1747 $conf = $snap;
1748 }
1749 my $storage_cfg = PVE::Storage::config();
1750 #Maybe include later
1751 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1752 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
1753
1754 return {
1755 hasFeature => $hasFeature,
1756 #nodes => [ keys %$nodelist ],
1757 };
1758 }});
1759 1;