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