]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
876849dedd064ab60a1aac1816fe065499081736
[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::LXC::Create;
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::LXC::Create::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::LXC::Create::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::update_lxc_config($storage_cfg, $vmid, $conf);
1009
1010 my $cmd = ['lxc-start', '-n', $vmid];
1011
1012 run_command($cmd);
1013
1014 return;
1015 };
1016
1017 return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
1018 }
1019 }});
1020
1021 __PACKAGE__->register_method({
1022 name => 'vm_stop',
1023 path => '{vmid}/status/stop',
1024 method => 'POST',
1025 protected => 1,
1026 proxyto => 'node',
1027 description => "Stop the container.",
1028 permissions => {
1029 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1030 },
1031 parameters => {
1032 additionalProperties => 0,
1033 properties => {
1034 node => get_standard_option('pve-node'),
1035 vmid => get_standard_option('pve-vmid'),
1036 },
1037 },
1038 returns => {
1039 type => 'string',
1040 },
1041 code => sub {
1042 my ($param) = @_;
1043
1044 my $rpcenv = PVE::RPCEnvironment::get();
1045
1046 my $authuser = $rpcenv->get_user();
1047
1048 my $node = extract_param($param, 'node');
1049
1050 my $vmid = extract_param($param, 'vmid');
1051
1052 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1053
1054 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1055
1056 my $hacmd = sub {
1057 my $upid = shift;
1058
1059 my $service = "ct:$vmid";
1060
1061 my $cmd = ['ha-manager', 'disable', $service];
1062
1063 print "Executing HA stop for CT $vmid\n";
1064
1065 PVE::Tools::run_command($cmd);
1066
1067 return;
1068 };
1069
1070 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1071
1072 } else {
1073
1074 my $realcmd = sub {
1075 my $upid = shift;
1076
1077 syslog('info', "stoping CT $vmid: $upid\n");
1078
1079 my $conf = PVE::LXC::load_config($vmid);
1080
1081 my $storage_cfg = PVE::Storage::config();
1082
1083 my $cmd = ['lxc-stop', '-n', $vmid, '--kill'];
1084
1085 run_command($cmd);
1086
1087 return;
1088 };
1089
1090 return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
1091 }
1092 }});
1093
1094 __PACKAGE__->register_method({
1095 name => 'vm_shutdown',
1096 path => '{vmid}/status/shutdown',
1097 method => 'POST',
1098 protected => 1,
1099 proxyto => 'node',
1100 description => "Shutdown the container.",
1101 permissions => {
1102 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1103 },
1104 parameters => {
1105 additionalProperties => 0,
1106 properties => {
1107 node => get_standard_option('pve-node'),
1108 vmid => get_standard_option('pve-vmid'),
1109 timeout => {
1110 description => "Wait maximal timeout seconds.",
1111 type => 'integer',
1112 minimum => 0,
1113 optional => 1,
1114 default => 60,
1115 },
1116 forceStop => {
1117 description => "Make sure the Container stops.",
1118 type => 'boolean',
1119 optional => 1,
1120 default => 0,
1121 }
1122 },
1123 },
1124 returns => {
1125 type => 'string',
1126 },
1127 code => sub {
1128 my ($param) = @_;
1129
1130 my $rpcenv = PVE::RPCEnvironment::get();
1131
1132 my $authuser = $rpcenv->get_user();
1133
1134 my $node = extract_param($param, 'node');
1135
1136 my $vmid = extract_param($param, 'vmid');
1137
1138 my $timeout = extract_param($param, 'timeout');
1139
1140 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1141
1142 my $realcmd = sub {
1143 my $upid = shift;
1144
1145 syslog('info', "shutdown CT $vmid: $upid\n");
1146
1147 my $cmd = ['lxc-stop', '-n', $vmid];
1148
1149 $timeout = 60 if !defined($timeout);
1150
1151 my $conf = PVE::LXC::load_config($vmid);
1152
1153 my $storage_cfg = PVE::Storage::config();
1154
1155 push @$cmd, '--timeout', $timeout;
1156
1157 eval { run_command($cmd, timeout => $timeout+5); };
1158 my $err = $@;
1159 if ($err && $param->{forceStop}) {
1160 $err = undef;
1161 warn "shutdown failed - forcing stop now\n";
1162
1163 push @$cmd, '--kill';
1164 run_command($cmd);
1165
1166 }
1167
1168 die $err if !$err;
1169
1170 return;
1171 };
1172
1173 my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
1174
1175 return $upid;
1176 }});
1177
1178 __PACKAGE__->register_method({
1179 name => 'vm_suspend',
1180 path => '{vmid}/status/suspend',
1181 method => 'POST',
1182 protected => 1,
1183 proxyto => 'node',
1184 description => "Suspend the container.",
1185 permissions => {
1186 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1187 },
1188 parameters => {
1189 additionalProperties => 0,
1190 properties => {
1191 node => get_standard_option('pve-node'),
1192 vmid => get_standard_option('pve-vmid'),
1193 },
1194 },
1195 returns => {
1196 type => 'string',
1197 },
1198 code => sub {
1199 my ($param) = @_;
1200
1201 my $rpcenv = PVE::RPCEnvironment::get();
1202
1203 my $authuser = $rpcenv->get_user();
1204
1205 my $node = extract_param($param, 'node');
1206
1207 my $vmid = extract_param($param, 'vmid');
1208
1209 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1210
1211 my $realcmd = sub {
1212 my $upid = shift;
1213
1214 syslog('info', "suspend CT $vmid: $upid\n");
1215
1216 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump'];
1217
1218 run_command($cmd);
1219
1220 return;
1221 };
1222
1223 my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
1224
1225 return $upid;
1226 }});
1227
1228 __PACKAGE__->register_method({
1229 name => 'vm_resume',
1230 path => '{vmid}/status/resume',
1231 method => 'POST',
1232 protected => 1,
1233 proxyto => 'node',
1234 description => "Resume the container.",
1235 permissions => {
1236 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1237 },
1238 parameters => {
1239 additionalProperties => 0,
1240 properties => {
1241 node => get_standard_option('pve-node'),
1242 vmid => get_standard_option('pve-vmid'),
1243 },
1244 },
1245 returns => {
1246 type => 'string',
1247 },
1248 code => sub {
1249 my ($param) = @_;
1250
1251 my $rpcenv = PVE::RPCEnvironment::get();
1252
1253 my $authuser = $rpcenv->get_user();
1254
1255 my $node = extract_param($param, 'node');
1256
1257 my $vmid = extract_param($param, 'vmid');
1258
1259 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
1260
1261 my $realcmd = sub {
1262 my $upid = shift;
1263
1264 syslog('info', "resume CT $vmid: $upid\n");
1265
1266 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground',
1267 '-D', '/var/liv/vz/dump'];
1268
1269 run_command($cmd);
1270
1271 return;
1272 };
1273
1274 my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
1275
1276 return $upid;
1277 }});
1278
1279 __PACKAGE__->register_method({
1280 name => 'migrate_vm',
1281 path => '{vmid}/migrate',
1282 method => 'POST',
1283 protected => 1,
1284 proxyto => 'node',
1285 description => "Migrate the container to another node. Creates a new migration task.",
1286 permissions => {
1287 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1288 },
1289 parameters => {
1290 additionalProperties => 0,
1291 properties => {
1292 node => get_standard_option('pve-node'),
1293 vmid => get_standard_option('pve-vmid'),
1294 target => get_standard_option('pve-node', { description => "Target node." }),
1295 online => {
1296 type => 'boolean',
1297 description => "Use online/live migration.",
1298 optional => 1,
1299 },
1300 },
1301 },
1302 returns => {
1303 type => 'string',
1304 description => "the task ID.",
1305 },
1306 code => sub {
1307 my ($param) = @_;
1308
1309 my $rpcenv = PVE::RPCEnvironment::get();
1310
1311 my $authuser = $rpcenv->get_user();
1312
1313 my $target = extract_param($param, 'target');
1314
1315 my $localnode = PVE::INotify::nodename();
1316 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1317
1318 PVE::Cluster::check_cfs_quorum();
1319
1320 PVE::Cluster::check_node_exists($target);
1321
1322 my $targetip = PVE::Cluster::remote_node_ip($target);
1323
1324 my $vmid = extract_param($param, 'vmid');
1325
1326 # test if VM exists
1327 PVE::LXC::load_config($vmid);
1328
1329 # try to detect errors early
1330 if (PVE::LXC::check_running($vmid)) {
1331 die "cant migrate running container without --online\n"
1332 if !$param->{online};
1333 }
1334
1335 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1336
1337 my $hacmd = sub {
1338 my $upid = shift;
1339
1340 my $service = "ct:$vmid";
1341
1342 my $cmd = ['ha-manager', 'migrate', $service, $target];
1343
1344 print "Executing HA migrate for CT $vmid to node $target\n";
1345
1346 PVE::Tools::run_command($cmd);
1347
1348 return;
1349 };
1350
1351 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1352
1353 } else {
1354
1355 my $realcmd = sub {
1356 my $upid = shift;
1357
1358 # fixme: implement lxc container migration
1359 die "lxc container migration not implemented\n";
1360
1361 return;
1362 };
1363
1364 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
1365 }
1366 }});
1367
1368 __PACKAGE__->register_method({
1369 name => 'snapshot',
1370 path => '{vmid}/snapshot',
1371 method => 'POST',
1372 protected => 1,
1373 proxyto => 'node',
1374 description => "Snapshot a container.",
1375 permissions => {
1376 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1377 },
1378 parameters => {
1379 additionalProperties => 0,
1380 properties => {
1381 node => get_standard_option('pve-node'),
1382 vmid => get_standard_option('pve-vmid'),
1383 snapname => get_standard_option('pve-lxc-snapshot-name'),
1384 # vmstate => {
1385 # optional => 1,
1386 # type => 'boolean',
1387 # description => "Save the vmstate",
1388 # },
1389 description => {
1390 optional => 1,
1391 type => 'string',
1392 description => "A textual description or comment.",
1393 },
1394 },
1395 },
1396 returns => {
1397 type => 'string',
1398 description => "the task ID.",
1399 },
1400 code => sub {
1401 my ($param) = @_;
1402
1403 my $rpcenv = PVE::RPCEnvironment::get();
1404
1405 my $authuser = $rpcenv->get_user();
1406
1407 my $node = extract_param($param, 'node');
1408
1409 my $vmid = extract_param($param, 'vmid');
1410
1411 my $snapname = extract_param($param, 'snapname');
1412
1413 die "unable to use snapshot name 'current' (reserved name)\n"
1414 if $snapname eq 'current';
1415
1416 my $realcmd = sub {
1417 PVE::Cluster::log_msg('info', $authuser, "snapshot container $vmid: $snapname");
1418 PVE::LXC::snapshot_create($vmid, $snapname, $param->{description});
1419 };
1420
1421 return $rpcenv->fork_worker('pctsnapshot', $vmid, $authuser, $realcmd);
1422 }});
1423
1424 __PACKAGE__->register_method({
1425 name => 'delsnapshot',
1426 path => '{vmid}/snapshot/{snapname}',
1427 method => 'DELETE',
1428 protected => 1,
1429 proxyto => 'node',
1430 description => "Delete a LXC snapshot.",
1431 permissions => {
1432 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1433 },
1434 parameters => {
1435 additionalProperties => 0,
1436 properties => {
1437 node => get_standard_option('pve-node'),
1438 vmid => get_standard_option('pve-vmid'),
1439 snapname => get_standard_option('pve-lxc-snapshot-name'),
1440 force => {
1441 optional => 1,
1442 type => 'boolean',
1443 description => "For removal from config file, even if removing disk snapshots fails.",
1444 },
1445 },
1446 },
1447 returns => {
1448 type => 'string',
1449 description => "the task ID.",
1450 },
1451 code => sub {
1452 my ($param) = @_;
1453
1454 my $rpcenv = PVE::RPCEnvironment::get();
1455
1456 my $authuser = $rpcenv->get_user();
1457
1458 my $node = extract_param($param, 'node');
1459
1460 my $vmid = extract_param($param, 'vmid');
1461
1462 my $snapname = extract_param($param, 'snapname');
1463
1464 my $realcmd = sub {
1465 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
1466 PVE::LXC::snapshot_delete($vmid, $snapname, $param->{force});
1467 };
1468
1469 return $rpcenv->fork_worker('lxcdelsnapshot', $vmid, $authuser, $realcmd);
1470 }});
1471
1472 __PACKAGE__->register_method({
1473 name => 'rollback',
1474 path => '{vmid}/snapshot/{snapname}/rollback',
1475 method => 'POST',
1476 protected => 1,
1477 proxyto => 'node',
1478 description => "Rollback LXC state to specified snapshot.",
1479 permissions => {
1480 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1481 },
1482 parameters => {
1483 additionalProperties => 0,
1484 properties => {
1485 node => get_standard_option('pve-node'),
1486 vmid => get_standard_option('pve-vmid'),
1487 snapname => get_standard_option('pve-lxc-snapshot-name'),
1488 },
1489 },
1490 returns => {
1491 type => 'string',
1492 description => "the task ID.",
1493 },
1494 code => sub {
1495 my ($param) = @_;
1496
1497 my $rpcenv = PVE::RPCEnvironment::get();
1498
1499 my $authuser = $rpcenv->get_user();
1500
1501 my $node = extract_param($param, 'node');
1502
1503 my $vmid = extract_param($param, 'vmid');
1504
1505 my $snapname = extract_param($param, 'snapname');
1506
1507 my $realcmd = sub {
1508 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot LXC $vmid: $snapname");
1509 PVE::LXC::snapshot_rollback($vmid, $snapname);
1510 };
1511
1512 return $rpcenv->fork_worker('lxcrollback', $vmid, $authuser, $realcmd);
1513 }});
1514
1515 __PACKAGE__->register_method({
1516 name => 'snapshot_list',
1517 path => '{vmid}/snapshot',
1518 method => 'GET',
1519 description => "List all snapshots.",
1520 permissions => {
1521 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1522 },
1523 proxyto => 'node',
1524 protected => 1, # lxc pid files are only readable by root
1525 parameters => {
1526 additionalProperties => 0,
1527 properties => {
1528 vmid => get_standard_option('pve-vmid'),
1529 node => get_standard_option('pve-node'),
1530 },
1531 },
1532 returns => {
1533 type => 'array',
1534 items => {
1535 type => "object",
1536 properties => {},
1537 },
1538 links => [ { rel => 'child', href => "{name}" } ],
1539 },
1540 code => sub {
1541 my ($param) = @_;
1542
1543 my $vmid = $param->{vmid};
1544
1545 my $conf = PVE::LXC::load_config($vmid);
1546 my $snaphash = $conf->{snapshots} || {};
1547
1548 my $res = [];
1549
1550 foreach my $name (keys %$snaphash) {
1551 my $d = $snaphash->{$name};
1552 my $item = {
1553 name => $name,
1554 snaptime => $d->{snaptime} || 0,
1555 description => $d->{description} || '',
1556 };
1557 $item->{parent} = $d->{parent} if defined($d->{parent});
1558 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
1559 push @$res, $item;
1560 }
1561
1562 my $running = PVE::LXC::check_running($vmid) ? 1 : 0;
1563 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
1564 $current->{parent} = $conf->{parent} if defined($conf->{parent});
1565
1566 push @$res, $current;
1567
1568 return $res;
1569 }});
1570
1571 __PACKAGE__->register_method({
1572 name => 'snapshot_cmd_idx',
1573 path => '{vmid}/snapshot/{snapname}',
1574 description => '',
1575 method => 'GET',
1576 permissions => {
1577 user => 'all',
1578 },
1579 parameters => {
1580 additionalProperties => 0,
1581 properties => {
1582 vmid => get_standard_option('pve-vmid'),
1583 node => get_standard_option('pve-node'),
1584 snapname => get_standard_option('pve-lxc-snapshot-name'),
1585 },
1586 },
1587 returns => {
1588 type => 'array',
1589 items => {
1590 type => "object",
1591 properties => {},
1592 },
1593 links => [ { rel => 'child', href => "{cmd}" } ],
1594 },
1595 code => sub {
1596 my ($param) = @_;
1597
1598 my $res = [];
1599
1600 push @$res, { cmd => 'rollback' };
1601 push @$res, { cmd => 'config' };
1602
1603 return $res;
1604 }});
1605
1606 __PACKAGE__->register_method({
1607 name => 'update_snapshot_config',
1608 path => '{vmid}/snapshot/{snapname}/config',
1609 method => 'PUT',
1610 protected => 1,
1611 proxyto => 'node',
1612 description => "Update snapshot metadata.",
1613 permissions => {
1614 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1615 },
1616 parameters => {
1617 additionalProperties => 0,
1618 properties => {
1619 node => get_standard_option('pve-node'),
1620 vmid => get_standard_option('pve-vmid'),
1621 snapname => get_standard_option('pve-lxc-snapshot-name'),
1622 description => {
1623 optional => 1,
1624 type => 'string',
1625 description => "A textual description or comment.",
1626 },
1627 },
1628 },
1629 returns => { type => 'null' },
1630 code => sub {
1631 my ($param) = @_;
1632
1633 my $rpcenv = PVE::RPCEnvironment::get();
1634
1635 my $authuser = $rpcenv->get_user();
1636
1637 my $vmid = extract_param($param, 'vmid');
1638
1639 my $snapname = extract_param($param, 'snapname');
1640
1641 return undef if !defined($param->{description});
1642
1643 my $updatefn = sub {
1644
1645 my $conf = PVE::LXC::load_config($vmid);
1646 PVE::LXC::check_lock($conf);
1647
1648 my $snap = $conf->{snapshots}->{$snapname};
1649
1650 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1651
1652 $snap->{description} = $param->{description} if defined($param->{description});
1653
1654 PVE::LXC::write_config($vmid, $conf, 1);
1655 };
1656
1657 PVE::LXC::lock_container($vmid, 10, $updatefn);
1658
1659 return undef;
1660 }});
1661
1662 __PACKAGE__->register_method({
1663 name => 'get_snapshot_config',
1664 path => '{vmid}/snapshot/{snapname}/config',
1665 method => 'GET',
1666 proxyto => 'node',
1667 description => "Get snapshot configuration",
1668 permissions => {
1669 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1670 },
1671 parameters => {
1672 additionalProperties => 0,
1673 properties => {
1674 node => get_standard_option('pve-node'),
1675 vmid => get_standard_option('pve-vmid'),
1676 snapname => get_standard_option('pve-lxc-snapshot-name'),
1677 },
1678 },
1679 returns => { type => "object" },
1680 code => sub {
1681 my ($param) = @_;
1682
1683 my $rpcenv = PVE::RPCEnvironment::get();
1684
1685 my $authuser = $rpcenv->get_user();
1686
1687 my $vmid = extract_param($param, 'vmid');
1688
1689 my $snapname = extract_param($param, 'snapname');
1690
1691 my $conf = PVE::LXC::load_config($vmid);
1692
1693 my $snap = $conf->{snapshots}->{$snapname};
1694
1695 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1696
1697 delete $snap->{lxc};
1698
1699 return $snap;
1700 }});
1701
1702 __PACKAGE__->register_method({
1703 name => 'vm_feature',
1704 path => '{vmid}/feature',
1705 method => 'GET',
1706 proxyto => 'node',
1707 protected => 1,
1708 description => "Check if feature for virtual machine is available.",
1709 permissions => {
1710 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1711 },
1712 parameters => {
1713 additionalProperties => 0,
1714 properties => {
1715 node => get_standard_option('pve-node'),
1716 vmid => get_standard_option('pve-vmid'),
1717 feature => {
1718 description => "Feature to check.",
1719 type => 'string',
1720 enum => [ 'snapshot' ],
1721 },
1722 snapname => get_standard_option('pve-lxc-snapshot-name', {
1723 optional => 1,
1724 }),
1725 },
1726 },
1727 returns => {
1728 type => "object",
1729 properties => {
1730 hasFeature => { type => 'boolean' },
1731 #nodes => {
1732 #type => 'array',
1733 #items => { type => 'string' },
1734 #}
1735 },
1736 },
1737 code => sub {
1738 my ($param) = @_;
1739
1740 my $node = extract_param($param, 'node');
1741
1742 my $vmid = extract_param($param, 'vmid');
1743
1744 my $snapname = extract_param($param, 'snapname');
1745
1746 my $feature = extract_param($param, 'feature');
1747
1748 my $conf = PVE::LXC::load_config($vmid);
1749
1750 if($snapname){
1751 my $snap = $conf->{snapshots}->{$snapname};
1752 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1753 $conf = $snap;
1754 }
1755 my $storage_cfg = PVE::Storage::config();
1756 #Maybe include later
1757 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1758 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
1759
1760 return {
1761 hasFeature => $hasFeature,
1762 #nodes => [ keys %$nodelist ],
1763 };
1764 }});
1765
1766 __PACKAGE__->register_method({
1767 name => 'template',
1768 path => '{vmid}/template',
1769 method => 'POST',
1770 protected => 1,
1771 proxyto => 'node',
1772 description => "Create a Template.",
1773 permissions => {
1774 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
1775 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1776 },
1777 parameters => {
1778 additionalProperties => 0,
1779 properties => {
1780 node => get_standard_option('pve-node'),
1781 vmid => get_standard_option('pve-vmid'),
1782 },
1783 },
1784 returns => { type => 'null'},
1785 code => sub {
1786 my ($param) = @_;
1787
1788 my $rpcenv = PVE::RPCEnvironment::get();
1789
1790 my $authuser = $rpcenv->get_user();
1791
1792 my $node = extract_param($param, 'node');
1793
1794 my $vmid = extract_param($param, 'vmid');
1795
1796 my $updatefn = sub {
1797
1798 my $conf = PVE::LXC::load_config($vmid);
1799 PVE::LXC::check_lock($conf);
1800
1801 die "unable to create template, because CT contains snapshots\n"
1802 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
1803
1804 die "you can't convert a template to a template\n"
1805 if PVE::LXC::is_template($conf);
1806
1807 die "you can't convert a CT to template if the CT is running\n"
1808 if PVE::LXC::check_running($vmid);
1809
1810 my $realcmd = sub {
1811 PVE::LXC::template_create($vmid, $conf);
1812 };
1813
1814 $conf->{template} = 1;
1815
1816 PVE::LXC::write_config($vmid, $conf);
1817 # and remove lxc config
1818 PVE::LXC::update_lxc_config(undef, $vmid, $conf);
1819
1820 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1821 };
1822
1823 PVE::LXC::lock_container($vmid, undef, $updatefn);
1824
1825 return undef;
1826 }});
1827
1828 1;