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