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