]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC.pm
implement template creation
[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} = print_ct_mountpount({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_from_pool($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 $shcmd = [ '/usr/bin/dtach', '-A',
736 "/var/run/dtach/vzctlconsole$vmid",
737 '-r', 'winch', '-z',
738 'lxc-console', '-n', $vmid ];
739
740 my $realcmd = sub {
741 my $upid = shift;
742
743 syslog ('info', "starting lxc vnc proxy $upid\n");
744
745 my $timeout = 10;
746
747 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
748 '-timeout', $timeout, '-authpath', $authpath,
749 '-perm', 'VM.Console'];
750
751 if ($param->{websocket}) {
752 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
753 push @$cmd, '-notls', '-listen', 'localhost';
754 }
755
756 push @$cmd, '-c', @$remcmd, @$shcmd;
757
758 run_command($cmd);
759
760 return;
761 };
762
763 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
764
765 PVE::Tools::wait_for_vnc_port($port);
766
767 return {
768 user => $authuser,
769 ticket => $ticket,
770 port => $port,
771 upid => $upid,
772 cert => $sslcert,
773 };
774 }});
775
776 __PACKAGE__->register_method({
777 name => 'vncwebsocket',
778 path => '{vmid}/vncwebsocket',
779 method => 'GET',
780 permissions => {
781 description => "You also need to pass a valid ticket (vncticket).",
782 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
783 },
784 description => "Opens a weksocket for VNC traffic.",
785 parameters => {
786 additionalProperties => 0,
787 properties => {
788 node => get_standard_option('pve-node'),
789 vmid => get_standard_option('pve-vmid'),
790 vncticket => {
791 description => "Ticket from previous call to vncproxy.",
792 type => 'string',
793 maxLength => 512,
794 },
795 port => {
796 description => "Port number returned by previous vncproxy call.",
797 type => 'integer',
798 minimum => 5900,
799 maximum => 5999,
800 },
801 },
802 },
803 returns => {
804 type => "object",
805 properties => {
806 port => { type => 'string' },
807 },
808 },
809 code => sub {
810 my ($param) = @_;
811
812 my $rpcenv = PVE::RPCEnvironment::get();
813
814 my $authuser = $rpcenv->get_user();
815
816 my $authpath = "/vms/$param->{vmid}";
817
818 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
819
820 my $port = $param->{port};
821
822 return { port => $port };
823 }});
824
825 __PACKAGE__->register_method ({
826 name => 'spiceproxy',
827 path => '{vmid}/spiceproxy',
828 method => 'POST',
829 protected => 1,
830 proxyto => 'node',
831 permissions => {
832 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
833 },
834 description => "Returns a SPICE configuration to connect to the CT.",
835 parameters => {
836 additionalProperties => 0,
837 properties => {
838 node => get_standard_option('pve-node'),
839 vmid => get_standard_option('pve-vmid'),
840 proxy => get_standard_option('spice-proxy', { optional => 1 }),
841 },
842 },
843 returns => get_standard_option('remote-viewer-config'),
844 code => sub {
845 my ($param) = @_;
846
847 my $vmid = $param->{vmid};
848 my $node = $param->{node};
849 my $proxy = $param->{proxy};
850
851 my $authpath = "/vms/$vmid";
852 my $permissions = 'VM.Console';
853
854 my $shcmd = ['/usr/bin/dtach', '-A',
855 "/var/run/dtach/vzctlconsole$vmid",
856 '-r', 'winch', '-z',
857 'lxc-console', '-n', $vmid];
858
859 my $title = "CT $vmid";
860
861 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
862 }});
863
864 __PACKAGE__->register_method({
865 name => 'vmcmdidx',
866 path => '{vmid}/status',
867 method => 'GET',
868 proxyto => 'node',
869 description => "Directory index",
870 permissions => {
871 user => 'all',
872 },
873 parameters => {
874 additionalProperties => 0,
875 properties => {
876 node => get_standard_option('pve-node'),
877 vmid => get_standard_option('pve-vmid'),
878 },
879 },
880 returns => {
881 type => 'array',
882 items => {
883 type => "object",
884 properties => {
885 subdir => { type => 'string' },
886 },
887 },
888 links => [ { rel => 'child', href => "{subdir}" } ],
889 },
890 code => sub {
891 my ($param) = @_;
892
893 # test if VM exists
894 my $conf = PVE::LXC::load_config($param->{vmid});
895
896 my $res = [
897 { subdir => 'current' },
898 { subdir => 'start' },
899 { subdir => 'stop' },
900 { subdir => 'shutdown' },
901 { subdir => 'migrate' },
902 ];
903
904 return $res;
905 }});
906
907 __PACKAGE__->register_method({
908 name => 'vm_status',
909 path => '{vmid}/status/current',
910 method => 'GET',
911 proxyto => 'node',
912 protected => 1, # openvz /proc entries are only readable by root
913 description => "Get virtual machine status.",
914 permissions => {
915 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
916 },
917 parameters => {
918 additionalProperties => 0,
919 properties => {
920 node => get_standard_option('pve-node'),
921 vmid => get_standard_option('pve-vmid'),
922 },
923 },
924 returns => { type => 'object' },
925 code => sub {
926 my ($param) = @_;
927
928 # test if VM exists
929 my $conf = PVE::LXC::load_config($param->{vmid});
930
931 my $vmstatus = PVE::LXC::vmstatus($param->{vmid});
932 my $status = $vmstatus->{$param->{vmid}};
933
934 $status->{ha} = PVE::HA::Config::vm_is_ha_managed($param->{vmid}) ? 1 : 0;
935
936 return $status;
937 }});
938
939 __PACKAGE__->register_method({
940 name => 'vm_start',
941 path => '{vmid}/status/start',
942 method => 'POST',
943 protected => 1,
944 proxyto => 'node',
945 description => "Start the container.",
946 permissions => {
947 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
948 },
949 parameters => {
950 additionalProperties => 0,
951 properties => {
952 node => get_standard_option('pve-node'),
953 vmid => get_standard_option('pve-vmid'),
954 },
955 },
956 returns => {
957 type => 'string',
958 },
959 code => sub {
960 my ($param) = @_;
961
962 my $rpcenv = PVE::RPCEnvironment::get();
963
964 my $authuser = $rpcenv->get_user();
965
966 my $node = extract_param($param, 'node');
967
968 my $vmid = extract_param($param, 'vmid');
969
970 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
971
972 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
973
974 my $hacmd = sub {
975 my $upid = shift;
976
977 my $service = "ct:$vmid";
978
979 my $cmd = ['ha-manager', 'enable', $service];
980
981 print "Executing HA start for CT $vmid\n";
982
983 PVE::Tools::run_command($cmd);
984
985 return;
986 };
987
988 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
989
990 } else {
991
992 my $realcmd = sub {
993 my $upid = shift;
994
995 syslog('info', "starting CT $vmid: $upid\n");
996
997 my $conf = PVE::LXC::load_config($vmid);
998
999 die "you can't start a CT if it's a template\n"
1000 if PVE::LXC::is_template($conf);
1001
1002 my $storage_cfg = cfs_read_file("storage.cfg");
1003
1004 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1005 PVE::Storage::activate_volumes($storage_cfg, [$rootinfo->{volume}]);
1006
1007 PVE::LXC::update_lxc_config($storage_cfg, $vmid, $conf);
1008
1009 my $cmd = ['lxc-start', '-n', $vmid];
1010
1011 run_command($cmd);
1012
1013 return;
1014 };
1015
1016 return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd);
1017 }
1018 }});
1019
1020 __PACKAGE__->register_method({
1021 name => 'vm_stop',
1022 path => '{vmid}/status/stop',
1023 method => 'POST',
1024 protected => 1,
1025 proxyto => 'node',
1026 description => "Stop the container.",
1027 permissions => {
1028 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1029 },
1030 parameters => {
1031 additionalProperties => 0,
1032 properties => {
1033 node => get_standard_option('pve-node'),
1034 vmid => get_standard_option('pve-vmid'),
1035 },
1036 },
1037 returns => {
1038 type => 'string',
1039 },
1040 code => sub {
1041 my ($param) = @_;
1042
1043 my $rpcenv = PVE::RPCEnvironment::get();
1044
1045 my $authuser = $rpcenv->get_user();
1046
1047 my $node = extract_param($param, 'node');
1048
1049 my $vmid = extract_param($param, 'vmid');
1050
1051 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1052
1053 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1054
1055 my $hacmd = sub {
1056 my $upid = shift;
1057
1058 my $service = "ct:$vmid";
1059
1060 my $cmd = ['ha-manager', 'disable', $service];
1061
1062 print "Executing HA stop for CT $vmid\n";
1063
1064 PVE::Tools::run_command($cmd);
1065
1066 return;
1067 };
1068
1069 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
1070
1071 } else {
1072
1073 my $realcmd = sub {
1074 my $upid = shift;
1075
1076 syslog('info', "stoping CT $vmid: $upid\n");
1077
1078 my $conf = PVE::LXC::load_config($vmid);
1079
1080 my $storage_cfg = PVE::Storage::config();
1081
1082 my $cmd = ['lxc-stop', '-n', $vmid, '--kill'];
1083
1084 run_command($cmd);
1085
1086 PVE::LXC::vm_stop_cleanup($storage_cfg, $vmid, $conf);
1087
1088 return;
1089 };
1090
1091 return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd);
1092 }
1093 }});
1094
1095 __PACKAGE__->register_method({
1096 name => 'vm_shutdown',
1097 path => '{vmid}/status/shutdown',
1098 method => 'POST',
1099 protected => 1,
1100 proxyto => 'node',
1101 description => "Shutdown the container.",
1102 permissions => {
1103 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1104 },
1105 parameters => {
1106 additionalProperties => 0,
1107 properties => {
1108 node => get_standard_option('pve-node'),
1109 vmid => get_standard_option('pve-vmid'),
1110 timeout => {
1111 description => "Wait maximal timeout seconds.",
1112 type => 'integer',
1113 minimum => 0,
1114 optional => 1,
1115 default => 60,
1116 },
1117 forceStop => {
1118 description => "Make sure the Container stops.",
1119 type => 'boolean',
1120 optional => 1,
1121 default => 0,
1122 }
1123 },
1124 },
1125 returns => {
1126 type => 'string',
1127 },
1128 code => sub {
1129 my ($param) = @_;
1130
1131 my $rpcenv = PVE::RPCEnvironment::get();
1132
1133 my $authuser = $rpcenv->get_user();
1134
1135 my $node = extract_param($param, 'node');
1136
1137 my $vmid = extract_param($param, 'vmid');
1138
1139 my $timeout = extract_param($param, 'timeout');
1140
1141 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1142
1143 my $realcmd = sub {
1144 my $upid = shift;
1145
1146 syslog('info', "shutdown CT $vmid: $upid\n");
1147
1148 my $cmd = ['lxc-stop', '-n', $vmid];
1149
1150 $timeout = 60 if !defined($timeout);
1151
1152 my $conf = PVE::LXC::load_config($vmid);
1153
1154 my $storage_cfg = PVE::Storage::config();
1155
1156 push @$cmd, '--timeout', $timeout;
1157
1158 eval { run_command($cmd, timeout => $timeout+5); };
1159 my $err = $@;
1160 if ($err && $param->{forceStop}) {
1161 $err = undef;
1162 warn "shutdown failed - forcing stop now\n";
1163
1164 push @$cmd, '--kill';
1165 run_command($cmd);
1166
1167 }
1168
1169 PVE::LXC::vm_stop_cleanup($storage_cfg, $vmid, $conf);
1170
1171 die $err if !$err;
1172
1173 return;
1174 };
1175
1176 my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd);
1177
1178 return $upid;
1179 }});
1180
1181 __PACKAGE__->register_method({
1182 name => 'vm_suspend',
1183 path => '{vmid}/status/suspend',
1184 method => 'POST',
1185 protected => 1,
1186 proxyto => 'node',
1187 description => "Suspend the container.",
1188 permissions => {
1189 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1190 },
1191 parameters => {
1192 additionalProperties => 0,
1193 properties => {
1194 node => get_standard_option('pve-node'),
1195 vmid => get_standard_option('pve-vmid'),
1196 },
1197 },
1198 returns => {
1199 type => 'string',
1200 },
1201 code => sub {
1202 my ($param) = @_;
1203
1204 my $rpcenv = PVE::RPCEnvironment::get();
1205
1206 my $authuser = $rpcenv->get_user();
1207
1208 my $node = extract_param($param, 'node');
1209
1210 my $vmid = extract_param($param, 'vmid');
1211
1212 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1213
1214 my $realcmd = sub {
1215 my $upid = shift;
1216
1217 syslog('info', "suspend CT $vmid: $upid\n");
1218
1219 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump'];
1220
1221 run_command($cmd);
1222
1223 return;
1224 };
1225
1226 my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
1227
1228 return $upid;
1229 }});
1230
1231 __PACKAGE__->register_method({
1232 name => 'vm_resume',
1233 path => '{vmid}/status/resume',
1234 method => 'POST',
1235 protected => 1,
1236 proxyto => 'node',
1237 description => "Resume the container.",
1238 permissions => {
1239 check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
1240 },
1241 parameters => {
1242 additionalProperties => 0,
1243 properties => {
1244 node => get_standard_option('pve-node'),
1245 vmid => get_standard_option('pve-vmid'),
1246 },
1247 },
1248 returns => {
1249 type => 'string',
1250 },
1251 code => sub {
1252 my ($param) = @_;
1253
1254 my $rpcenv = PVE::RPCEnvironment::get();
1255
1256 my $authuser = $rpcenv->get_user();
1257
1258 my $node = extract_param($param, 'node');
1259
1260 my $vmid = extract_param($param, 'vmid');
1261
1262 die "CT $vmid already running\n" if PVE::LXC::check_running($vmid);
1263
1264 my $realcmd = sub {
1265 my $upid = shift;
1266
1267 syslog('info', "resume CT $vmid: $upid\n");
1268
1269 my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground',
1270 '-D', '/var/liv/vz/dump'];
1271
1272 run_command($cmd);
1273
1274 return;
1275 };
1276
1277 my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
1278
1279 return $upid;
1280 }});
1281
1282 __PACKAGE__->register_method({
1283 name => 'migrate_vm',
1284 path => '{vmid}/migrate',
1285 method => 'POST',
1286 protected => 1,
1287 proxyto => 'node',
1288 description => "Migrate the container to another node. Creates a new migration task.",
1289 permissions => {
1290 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1291 },
1292 parameters => {
1293 additionalProperties => 0,
1294 properties => {
1295 node => get_standard_option('pve-node'),
1296 vmid => get_standard_option('pve-vmid'),
1297 target => get_standard_option('pve-node', { description => "Target node." }),
1298 online => {
1299 type => 'boolean',
1300 description => "Use online/live migration.",
1301 optional => 1,
1302 },
1303 },
1304 },
1305 returns => {
1306 type => 'string',
1307 description => "the task ID.",
1308 },
1309 code => sub {
1310 my ($param) = @_;
1311
1312 my $rpcenv = PVE::RPCEnvironment::get();
1313
1314 my $authuser = $rpcenv->get_user();
1315
1316 my $target = extract_param($param, 'target');
1317
1318 my $localnode = PVE::INotify::nodename();
1319 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
1320
1321 PVE::Cluster::check_cfs_quorum();
1322
1323 PVE::Cluster::check_node_exists($target);
1324
1325 my $targetip = PVE::Cluster::remote_node_ip($target);
1326
1327 my $vmid = extract_param($param, 'vmid');
1328
1329 # test if VM exists
1330 PVE::LXC::load_config($vmid);
1331
1332 # try to detect errors early
1333 if (PVE::LXC::check_running($vmid)) {
1334 die "cant migrate running container without --online\n"
1335 if !$param->{online};
1336 }
1337
1338 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1339
1340 my $hacmd = sub {
1341 my $upid = shift;
1342
1343 my $service = "ct:$vmid";
1344
1345 my $cmd = ['ha-manager', 'migrate', $service, $target];
1346
1347 print "Executing HA migrate for CT $vmid to node $target\n";
1348
1349 PVE::Tools::run_command($cmd);
1350
1351 return;
1352 };
1353
1354 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1355
1356 } else {
1357
1358 my $realcmd = sub {
1359 my $upid = shift;
1360
1361 # fixme: implement lxc container migration
1362 die "lxc container migration not implemented\n";
1363
1364 return;
1365 };
1366
1367 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
1368 }
1369 }});
1370
1371 __PACKAGE__->register_method({
1372 name => 'snapshot',
1373 path => '{vmid}/snapshot',
1374 method => 'POST',
1375 protected => 1,
1376 proxyto => 'node',
1377 description => "Snapshot a container.",
1378 permissions => {
1379 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1380 },
1381 parameters => {
1382 additionalProperties => 0,
1383 properties => {
1384 node => get_standard_option('pve-node'),
1385 vmid => get_standard_option('pve-vmid'),
1386 snapname => get_standard_option('pve-lxc-snapshot-name'),
1387 # vmstate => {
1388 # optional => 1,
1389 # type => 'boolean',
1390 # description => "Save the vmstate",
1391 # },
1392 description => {
1393 optional => 1,
1394 type => 'string',
1395 description => "A textual description or comment.",
1396 },
1397 },
1398 },
1399 returns => {
1400 type => 'string',
1401 description => "the task ID.",
1402 },
1403 code => sub {
1404 my ($param) = @_;
1405
1406 my $rpcenv = PVE::RPCEnvironment::get();
1407
1408 my $authuser = $rpcenv->get_user();
1409
1410 my $node = extract_param($param, 'node');
1411
1412 my $vmid = extract_param($param, 'vmid');
1413
1414 my $snapname = extract_param($param, 'snapname');
1415
1416 die "unable to use snapshot name 'current' (reserved name)\n"
1417 if $snapname eq 'current';
1418
1419 my $realcmd = sub {
1420 PVE::Cluster::log_msg('info', $authuser, "snapshot container $vmid: $snapname");
1421 PVE::LXC::snapshot_create($vmid, $snapname, $param->{description});
1422 };
1423
1424 return $rpcenv->fork_worker('pctsnapshot', $vmid, $authuser, $realcmd);
1425 }});
1426
1427 __PACKAGE__->register_method({
1428 name => 'delsnapshot',
1429 path => '{vmid}/snapshot/{snapname}',
1430 method => 'DELETE',
1431 protected => 1,
1432 proxyto => 'node',
1433 description => "Delete a LXC snapshot.",
1434 permissions => {
1435 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1436 },
1437 parameters => {
1438 additionalProperties => 0,
1439 properties => {
1440 node => get_standard_option('pve-node'),
1441 vmid => get_standard_option('pve-vmid'),
1442 snapname => get_standard_option('pve-lxc-snapshot-name'),
1443 force => {
1444 optional => 1,
1445 type => 'boolean',
1446 description => "For removal from config file, even if removing disk snapshots fails.",
1447 },
1448 },
1449 },
1450 returns => {
1451 type => 'string',
1452 description => "the task ID.",
1453 },
1454 code => sub {
1455 my ($param) = @_;
1456
1457 my $rpcenv = PVE::RPCEnvironment::get();
1458
1459 my $authuser = $rpcenv->get_user();
1460
1461 my $node = extract_param($param, 'node');
1462
1463 my $vmid = extract_param($param, 'vmid');
1464
1465 my $snapname = extract_param($param, 'snapname');
1466
1467 my $realcmd = sub {
1468 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
1469 PVE::LXC::snapshot_delete($vmid, $snapname, $param->{force});
1470 };
1471
1472 return $rpcenv->fork_worker('lxcdelsnapshot', $vmid, $authuser, $realcmd);
1473 }});
1474
1475 __PACKAGE__->register_method({
1476 name => 'rollback',
1477 path => '{vmid}/snapshot/{snapname}/rollback',
1478 method => 'POST',
1479 protected => 1,
1480 proxyto => 'node',
1481 description => "Rollback LXC state to specified snapshot.",
1482 permissions => {
1483 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1484 },
1485 parameters => {
1486 additionalProperties => 0,
1487 properties => {
1488 node => get_standard_option('pve-node'),
1489 vmid => get_standard_option('pve-vmid'),
1490 snapname => get_standard_option('pve-lxc-snapshot-name'),
1491 },
1492 },
1493 returns => {
1494 type => 'string',
1495 description => "the task ID.",
1496 },
1497 code => sub {
1498 my ($param) = @_;
1499
1500 my $rpcenv = PVE::RPCEnvironment::get();
1501
1502 my $authuser = $rpcenv->get_user();
1503
1504 my $node = extract_param($param, 'node');
1505
1506 my $vmid = extract_param($param, 'vmid');
1507
1508 my $snapname = extract_param($param, 'snapname');
1509
1510 my $realcmd = sub {
1511 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot LXC $vmid: $snapname");
1512 PVE::LXC::snapshot_rollback($vmid, $snapname);
1513 };
1514
1515 return $rpcenv->fork_worker('lxcrollback', $vmid, $authuser, $realcmd);
1516 }});
1517
1518 __PACKAGE__->register_method({
1519 name => 'snapshot_list',
1520 path => '{vmid}/snapshot',
1521 method => 'GET',
1522 description => "List all snapshots.",
1523 permissions => {
1524 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1525 },
1526 proxyto => 'node',
1527 protected => 1, # lxc pid files are only readable by root
1528 parameters => {
1529 additionalProperties => 0,
1530 properties => {
1531 vmid => get_standard_option('pve-vmid'),
1532 node => get_standard_option('pve-node'),
1533 },
1534 },
1535 returns => {
1536 type => 'array',
1537 items => {
1538 type => "object",
1539 properties => {},
1540 },
1541 links => [ { rel => 'child', href => "{name}" } ],
1542 },
1543 code => sub {
1544 my ($param) = @_;
1545
1546 my $vmid = $param->{vmid};
1547
1548 my $conf = PVE::LXC::load_config($vmid);
1549 my $snaphash = $conf->{snapshots} || {};
1550
1551 my $res = [];
1552
1553 foreach my $name (keys %$snaphash) {
1554 my $d = $snaphash->{$name};
1555 my $item = {
1556 name => $name,
1557 snaptime => $d->{snaptime} || 0,
1558 description => $d->{description} || '',
1559 };
1560 $item->{parent} = $d->{parent} if defined($d->{parent});
1561 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
1562 push @$res, $item;
1563 }
1564
1565 my $running = PVE::LXC::check_running($vmid) ? 1 : 0;
1566 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
1567 $current->{parent} = $conf->{parent} if defined($conf->{parent});
1568
1569 push @$res, $current;
1570
1571 return $res;
1572 }});
1573
1574 __PACKAGE__->register_method({
1575 name => 'snapshot_cmd_idx',
1576 path => '{vmid}/snapshot/{snapname}',
1577 description => '',
1578 method => 'GET',
1579 permissions => {
1580 user => 'all',
1581 },
1582 parameters => {
1583 additionalProperties => 0,
1584 properties => {
1585 vmid => get_standard_option('pve-vmid'),
1586 node => get_standard_option('pve-node'),
1587 snapname => get_standard_option('pve-lxc-snapshot-name'),
1588 },
1589 },
1590 returns => {
1591 type => 'array',
1592 items => {
1593 type => "object",
1594 properties => {},
1595 },
1596 links => [ { rel => 'child', href => "{cmd}" } ],
1597 },
1598 code => sub {
1599 my ($param) = @_;
1600
1601 my $res = [];
1602
1603 push @$res, { cmd => 'rollback' };
1604 push @$res, { cmd => 'config' };
1605
1606 return $res;
1607 }});
1608
1609 __PACKAGE__->register_method({
1610 name => 'update_snapshot_config',
1611 path => '{vmid}/snapshot/{snapname}/config',
1612 method => 'PUT',
1613 protected => 1,
1614 proxyto => 'node',
1615 description => "Update snapshot metadata.",
1616 permissions => {
1617 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1618 },
1619 parameters => {
1620 additionalProperties => 0,
1621 properties => {
1622 node => get_standard_option('pve-node'),
1623 vmid => get_standard_option('pve-vmid'),
1624 snapname => get_standard_option('pve-lxc-snapshot-name'),
1625 description => {
1626 optional => 1,
1627 type => 'string',
1628 description => "A textual description or comment.",
1629 },
1630 },
1631 },
1632 returns => { type => 'null' },
1633 code => sub {
1634 my ($param) = @_;
1635
1636 my $rpcenv = PVE::RPCEnvironment::get();
1637
1638 my $authuser = $rpcenv->get_user();
1639
1640 my $vmid = extract_param($param, 'vmid');
1641
1642 my $snapname = extract_param($param, 'snapname');
1643
1644 return undef if !defined($param->{description});
1645
1646 my $updatefn = sub {
1647
1648 my $conf = PVE::LXC::load_config($vmid);
1649 PVE::LXC::check_lock($conf);
1650
1651 my $snap = $conf->{snapshots}->{$snapname};
1652
1653 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1654
1655 $snap->{description} = $param->{description} if defined($param->{description});
1656
1657 PVE::LXC::write_config($vmid, $conf, 1);
1658 };
1659
1660 PVE::LXC::lock_container($vmid, 10, $updatefn);
1661
1662 return undef;
1663 }});
1664
1665 __PACKAGE__->register_method({
1666 name => 'get_snapshot_config',
1667 path => '{vmid}/snapshot/{snapname}/config',
1668 method => 'GET',
1669 proxyto => 'node',
1670 description => "Get snapshot configuration",
1671 permissions => {
1672 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
1673 },
1674 parameters => {
1675 additionalProperties => 0,
1676 properties => {
1677 node => get_standard_option('pve-node'),
1678 vmid => get_standard_option('pve-vmid'),
1679 snapname => get_standard_option('pve-lxc-snapshot-name'),
1680 },
1681 },
1682 returns => { type => "object" },
1683 code => sub {
1684 my ($param) = @_;
1685
1686 my $rpcenv = PVE::RPCEnvironment::get();
1687
1688 my $authuser = $rpcenv->get_user();
1689
1690 my $vmid = extract_param($param, 'vmid');
1691
1692 my $snapname = extract_param($param, 'snapname');
1693
1694 my $conf = PVE::LXC::load_config($vmid);
1695
1696 my $snap = $conf->{snapshots}->{$snapname};
1697
1698 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1699
1700 delete $snap->{lxc};
1701
1702 return $snap;
1703 }});
1704
1705 __PACKAGE__->register_method({
1706 name => 'vm_feature',
1707 path => '{vmid}/feature',
1708 method => 'GET',
1709 proxyto => 'node',
1710 protected => 1,
1711 description => "Check if feature for virtual machine is available.",
1712 permissions => {
1713 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1714 },
1715 parameters => {
1716 additionalProperties => 0,
1717 properties => {
1718 node => get_standard_option('pve-node'),
1719 vmid => get_standard_option('pve-vmid'),
1720 feature => {
1721 description => "Feature to check.",
1722 type => 'string',
1723 enum => [ 'snapshot' ],
1724 },
1725 snapname => get_standard_option('pve-lxc-snapshot-name', {
1726 optional => 1,
1727 }),
1728 },
1729 },
1730 returns => {
1731 type => "object",
1732 properties => {
1733 hasFeature => { type => 'boolean' },
1734 #nodes => {
1735 #type => 'array',
1736 #items => { type => 'string' },
1737 #}
1738 },
1739 },
1740 code => sub {
1741 my ($param) = @_;
1742
1743 my $node = extract_param($param, 'node');
1744
1745 my $vmid = extract_param($param, 'vmid');
1746
1747 my $snapname = extract_param($param, 'snapname');
1748
1749 my $feature = extract_param($param, 'feature');
1750
1751 my $conf = PVE::LXC::load_config($vmid);
1752
1753 if($snapname){
1754 my $snap = $conf->{snapshots}->{$snapname};
1755 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1756 $conf = $snap;
1757 }
1758 my $storage_cfg = PVE::Storage::config();
1759 #Maybe include later
1760 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1761 my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname);
1762
1763 return {
1764 hasFeature => $hasFeature,
1765 #nodes => [ keys %$nodelist ],
1766 };
1767 }});
1768
1769 __PACKAGE__->register_method({
1770 name => 'template',
1771 path => '{vmid}/template',
1772 method => 'POST',
1773 protected => 1,
1774 proxyto => 'node',
1775 description => "Create a Template.",
1776 permissions => {
1777 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
1778 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1779 },
1780 parameters => {
1781 additionalProperties => 0,
1782 properties => {
1783 node => get_standard_option('pve-node'),
1784 vmid => get_standard_option('pve-vmid'),
1785 },
1786 },
1787 returns => { type => 'null'},
1788 code => sub {
1789 my ($param) = @_;
1790
1791 my $rpcenv = PVE::RPCEnvironment::get();
1792
1793 my $authuser = $rpcenv->get_user();
1794
1795 my $node = extract_param($param, 'node');
1796
1797 my $vmid = extract_param($param, 'vmid');
1798
1799 my $updatefn = sub {
1800
1801 my $conf = PVE::LXC::load_config($vmid);
1802 PVE::LXC::check_lock($conf);
1803
1804 die "unable to create template, because CT contains snapshots\n"
1805 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
1806
1807 die "you can't convert a template to a template\n"
1808 if PVE::LXC::is_template($conf);
1809
1810 die "you can't convert a CT to template if the CT is running\n"
1811 if PVE::LXC::check_running($vmid);
1812
1813 my $realcmd = sub {
1814 PVE::LXC::template_create($vmid, $conf);
1815 };
1816
1817 $conf->{template} = 1;
1818
1819 PVE::LXC::write_config($vmid, $conf);
1820 # and remove lxc config
1821 PVE::LXC::update_lxc_config(undef, $vmid, $conf);
1822
1823 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1824 };
1825
1826 PVE::LXC::lock_container($vmid, undef, $updatefn);
1827
1828 return undef;
1829 }});
1830
1831 1;