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