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