]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
add code to use qemu-nbd with qcow2 images
[pve-container.git] / src / PVE / LXC.pm
1 package PVE::LXC;
2
3 use strict;
4 use warnings;
5
6 use File::Path;
7 use Fcntl ':flock';
8
9 use PVE::Cluster qw(cfs_register_file cfs_read_file);
10 use PVE::SafeSyslog;
11 use PVE::INotify;
12 use PVE::JSONSchema qw(get_standard_option);
13 use PVE::Tools qw($IPV6RE $IPV4RE);
14
15 use Data::Dumper;
16
17 cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
18
19 PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
20 sub verify_lxc_network {
21 my ($value, $noerr) = @_;
22
23 return $value if parse_lxc_network($value);
24
25 return undef if $noerr;
26
27 die "unable to parse network setting\n";
28 }
29
30 my $nodename = PVE::INotify::nodename();
31
32 sub parse_lxc_size {
33 my ($name, $value) = @_;
34
35 if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
36 my ($res, $unit) = ($1, lc($2 || 'b'));
37
38 return $res if $unit eq 'b';
39 return $res*1024 if $unit eq 'k';
40 return $res*1024*1024 if $unit eq 'm';
41 return $res*1024*1024*1024 if $unit eq 'g';
42 }
43
44 return undef;
45 }
46
47 my $valid_lxc_keys = {
48 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
49 'lxc.include' => 1,
50 'lxc.rootfs' => 1,
51 'lxc.mount' => 1,
52 'lxc.utsname' => 1,
53
54 'lxc.id_map' => 1,
55
56 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
57 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
58 'lxc.cgroup.cpu.cfs_period_us' => '\d+',
59 'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
60 'lxc.cgroup.cpu.shares' => '\d+',
61
62 # mount related
63 'lxc.mount' => 1,
64 'lxc.mount.entry' => 1,
65 'lxc.mount.auto' => 1,
66
67 # not used by pve
68 'lxc.tty' => '\d+',
69 'lxc.pts' => 1,
70 'lxc.haltsignal' => 1,
71 'lxc.rebootsignal' => 1,
72 'lxc.stopsignal' => 1,
73 'lxc.init_cmd' => 1,
74 'lxc.console' => 1,
75 'lxc.console.logfile' => 1,
76 'lxc.devttydir' => 1,
77 'lxc.autodev' => 1,
78 'lxc.kmsg' => 1,
79 'lxc.cap.drop' => 1,
80 'lxc.cap.keep' => 1,
81 'lxc.aa_profile' => 1,
82 'lxc.aa_allow_incomplete' => 1,
83 'lxc.se_context' => 1,
84 'lxc.loglevel' => 1,
85 'lxc.logfile' => 1,
86 'lxc.environment' => 1,
87
88
89 # autostart
90 'lxc.start.auto' => 1,
91 'lxc.start.delay' => 1,
92 'lxc.start.order' => 1,
93 'lxc.group' => 1,
94
95 # hooks
96 'lxc.hook.pre-start' => 1,
97 'lxc.hook.pre-mount' => 1,
98 'lxc.hook.mount' => 1,
99 'lxc.hook.autodev' => 1,
100 'lxc.hook.start' => 1,
101 'lxc.hook.post-stop' => 1,
102 'lxc.hook.clone' => 1,
103
104 # pve related keys
105 'pve.nameserver' => sub {
106 my ($name, $value) = @_;
107 return verify_nameserver_list($value);
108 },
109 'pve.searchdomain' => sub {
110 my ($name, $value) = @_;
111 return verify_searchdomain_list($value);
112 },
113 'pve.onboot' => '(0|1)',
114 'pve.startup' => sub {
115 my ($name, $value) = @_;
116 return PVE::JSONSchema::pve_verify_startup_order($value);
117 },
118 'pve.comment' => 1,
119 'pve.disksize' => '\d+(\.\d+)?',
120 };
121
122 my $valid_lxc_network_keys = {
123 type => 1,
124 mtu => 1,
125 name => 1, # ifname inside container
126 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
127 hwaddr => 1,
128 };
129
130 my $valid_pve_network_keys = {
131 bridge => 1,
132 tag => 1,
133 firewall => 1,
134 ip => 1,
135 gw => 1,
136 ip6 => 1,
137 gw6 => 1,
138 };
139
140 my $lxc_array_configs = {
141 'lxc.network' => 1,
142 'lxc.mount' => 1,
143 'lxc.include' => 1,
144 'lxc.id_map' => 1,
145 };
146
147 sub write_lxc_config {
148 my ($filename, $data) = @_;
149
150 my $raw = "";
151
152 return $raw if !$data;
153
154 my $done_hash = { digest => 1};
155
156 foreach my $k (sort keys %$data) {
157 next if $k !~ m/^lxc\./;
158 $done_hash->{$k} = 1;
159 if (ref($data->{$k})) {
160 die "got unexpected reference for '$k'" if !$lxc_array_configs->{$k};
161 foreach my $v (@{$data->{$k}}) {
162 $raw .= "$k = $v\n";
163 }
164 } else {
165 $raw .= "$k = $data->{$k}\n";
166 }
167 }
168
169 foreach my $k (sort keys %$data) {
170 next if $k !~ m/^pve\./;
171 $done_hash->{$k} = 1;
172 $raw .= "$k = $data->{$k}\n";
173 }
174
175 foreach my $k (sort keys %$data) {
176 next if $k !~ m/^net\d+$/;
177 $done_hash->{$k} = 1;
178 my $net = $data->{$k};
179 $raw .= "lxc.network.type = $net->{type}\n";
180 foreach my $subkey (sort keys %$net) {
181 next if $subkey eq 'type';
182 if ($valid_lxc_network_keys->{$subkey}) {
183 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
184 } elsif ($valid_pve_network_keys->{$subkey}) {
185 $raw .= "pve.network.$subkey = $net->{$subkey}\n";
186 } else {
187 die "found invalid network key '$subkey'";
188 }
189 }
190 }
191
192 foreach my $k (sort keys %$data) {
193 next if $done_hash->{$k};
194 die "found un-written value in config - implement this!";
195 }
196
197 return $raw;
198 }
199
200 sub parse_lxc_option {
201 my ($name, $value) = @_;
202
203 my $parser = $valid_lxc_keys->{$name};
204
205 die "inavlid key '$name'\n" if !defined($parser);
206
207 if ($parser eq '1') {
208 return $value;
209 } elsif (ref($parser)) {
210 my $res = &$parser($name, $value);
211 return $res if defined($res);
212 } else {
213 # assume regex
214 return $value if $value =~ m/^$parser$/;
215 }
216
217 die "unable to parse value '$value' for option '$name'\n";
218 }
219
220 sub parse_lxc_config {
221 my ($filename, $raw) = @_;
222
223 return undef if !defined($raw);
224
225 my $data = {
226 digest => Digest::SHA::sha1_hex($raw),
227 };
228
229 $filename =~ m|/lxc/(\d+)/config$|
230 || die "got strange filename '$filename'";
231
232 my $vmid = $1;
233
234 my $network_counter = 0;
235 my $network_list = [];
236 my $host_ifnames = {};
237
238 my $find_next_hostif_name = sub {
239 for (my $i = 0; $i < 10; $i++) {
240 my $name = "veth${vmid}.$i";
241 if (!$host_ifnames->{$name}) {
242 $host_ifnames->{$name} = 1;
243 return $name;
244 }
245 }
246
247 die "unable to find free host_ifname"; # should not happen
248 };
249
250 my $push_network = sub {
251 my ($netconf) = @_;
252 return if !$netconf;
253 push @{$network_list}, $netconf;
254 $network_counter++;
255 if (my $netname = $netconf->{'veth.pair'}) {
256 if ($netname =~ m/^veth(\d+).(\d)$/) {
257 die "wrong vmid for network interface pair\n" if $1 != $vmid;
258 my $host_ifnames->{$netname} = 1;
259 } else {
260 die "wrong network interface pair\n";
261 }
262 }
263 };
264
265 my $network;
266
267 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
268 my $line = $1;
269
270 next if $line =~ m/^\#/;
271 next if $line =~ m/^\s*$/;
272
273 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
274 my ($subkey, $value) = ($1, $2);
275 if ($subkey eq 'type') {
276 &$push_network($network);
277 $network = { type => $value };
278 } elsif ($valid_lxc_network_keys->{$subkey}) {
279 $network->{$subkey} = $value;
280 } else {
281 die "unable to parse config line: $line\n";
282 }
283 next;
284 }
285 if ($line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
286 my ($subkey, $value) = ($1, $2);
287 if ($valid_pve_network_keys->{$subkey}) {
288 $network->{$subkey} = $value;
289 } else {
290 die "unable to parse config line: $line\n";
291 }
292 next;
293 }
294 if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
295 my ($name, $value) = ($1, $2);
296 $data->{$name} = $value;
297 next;
298 }
299 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
300 my ($name, $value) = ($1, $2);
301
302 if ($lxc_array_configs->{$name}) {
303 $data->{$name} = [] if !defined($data->{$name});
304 push @{$data->{$name}}, parse_lxc_option($name, $value);
305 } else {
306 die "multiple definitions for $name\n" if defined($data->{$name});
307 $data->{$name} = parse_lxc_option($name, $value);
308 }
309
310 next;
311 }
312
313 die "unable to parse config line: $line\n";
314 }
315
316 &$push_network($network);
317
318 foreach my $net (@{$network_list}) {
319 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
320 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
321 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
322
323 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
324 $data->{"net$1"} = $net;
325 }
326 }
327
328 return $data;
329 }
330
331 sub config_list {
332 my $vmlist = PVE::Cluster::get_vmlist();
333 my $res = {};
334 return $res if !$vmlist || !$vmlist->{ids};
335 my $ids = $vmlist->{ids};
336
337 foreach my $vmid (keys %$ids) {
338 next if !$vmid; # skip CT0
339 my $d = $ids->{$vmid};
340 next if !$d->{node} || $d->{node} ne $nodename;
341 next if !$d->{type} || $d->{type} ne 'lxc';
342 $res->{$vmid}->{type} = 'lxc';
343 }
344 return $res;
345 }
346
347 sub cfs_config_path {
348 my ($vmid, $node) = @_;
349
350 $node = $nodename if !$node;
351 return "nodes/$node/lxc/$vmid/config";
352 }
353
354 sub config_file {
355 my ($vmid, $node) = @_;
356
357 my $cfspath = cfs_config_path($vmid, $node);
358 return "/etc/pve/$cfspath";
359 }
360
361 sub load_config {
362 my ($vmid) = @_;
363
364 my $cfspath = cfs_config_path($vmid);
365
366 my $conf = PVE::Cluster::cfs_read_file($cfspath);
367 die "container $vmid does not exists\n" if !defined($conf);
368
369 return $conf;
370 }
371
372 sub create_config {
373 my ($vmid, $conf) = @_;
374
375 my $dir = "/etc/pve/nodes/$nodename/lxc";
376 mkdir $dir;
377
378 $dir .= "/$vmid";
379 mkdir($dir) || die "unable to create container configuration directory - $!\n";
380
381 write_config($vmid, $conf);
382 }
383
384 sub destroy_config {
385 my ($vmid) = @_;
386
387 my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
388 File::Path::rmtree($dir);
389 }
390
391 sub write_config {
392 my ($vmid, $conf) = @_;
393
394 my $cfspath = cfs_config_path($vmid);
395
396 PVE::Cluster::cfs_write_file($cfspath, $conf);
397 }
398
399 my $tempcounter = 0;
400 sub write_temp_config {
401 my ($vmid, $conf) = @_;
402
403 $tempcounter++;
404 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
405
406 my $raw = write_lxc_config($filename, $conf);
407
408 PVE::Tools::file_set_contents($filename, $raw);
409
410 return $filename;
411 }
412
413 sub lock_container {
414 my ($vmid, $timeout, $code, @param) = @_;
415
416 my $lockdir = "/run/lock/lxc";
417 my $lockfile = "$lockdir/pve-config-{$vmid}.lock";
418
419 File::Path::make_path($lockdir);
420
421 my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param);
422
423 die $@ if $@;
424
425 return $res;
426 }
427
428 my $confdesc = {
429 onboot => {
430 optional => 1,
431 type => 'boolean',
432 description => "Specifies whether a VM will be started during system bootup.",
433 default => 0,
434 },
435 startup => get_standard_option('pve-startup-order'),
436 cpus => {
437 optional => 1,
438 type => 'integer',
439 description => "The number of CPUs for this container (0 is unlimited).",
440 minimum => 0,
441 maximum => 128,
442 default => 0,
443 },
444 cpuunits => {
445 optional => 1,
446 type => 'integer',
447 description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
448 minimum => 0,
449 maximum => 500000,
450 default => 1000,
451 },
452 memory => {
453 optional => 1,
454 type => 'integer',
455 description => "Amount of RAM for the VM in MB.",
456 minimum => 16,
457 default => 512,
458 },
459 swap => {
460 optional => 1,
461 type => 'integer',
462 description => "Amount of SWAP for the VM in MB.",
463 minimum => 0,
464 default => 512,
465 },
466 disk => {
467 optional => 1,
468 type => 'number',
469 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
470 minimum => 0,
471 default => 4,
472 },
473 hostname => {
474 optional => 1,
475 description => "Set a host name for the container.",
476 type => 'string',
477 maxLength => 255,
478 },
479 description => {
480 optional => 1,
481 type => 'string',
482 description => "Container description. Only used on the configuration web interface.",
483 },
484 searchdomain => {
485 optional => 1,
486 type => 'string',
487 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
488 },
489 nameserver => {
490 optional => 1,
491 type => 'string',
492 description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
493 },
494 };
495
496 my $MAX_LXC_NETWORKS = 10;
497 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
498 $confdesc->{"net$i"} = {
499 optional => 1,
500 type => 'string', format => 'pve-lxc-network',
501 description => "Specifies network interfaces for the container.",
502 };
503 }
504
505 sub option_exists {
506 my ($name) = @_;
507
508 return defined($confdesc->{$name});
509 }
510
511 # add JSON properties for create and set function
512 sub json_config_properties {
513 my $prop = shift;
514
515 foreach my $opt (keys %$confdesc) {
516 $prop->{$opt} = $confdesc->{$opt};
517 }
518
519 return $prop;
520 }
521
522 # container status helpers
523
524 sub list_active_containers {
525
526 my $filename = "/proc/net/unix";
527
528 # similar test is used by lcxcontainers.c: list_active_containers
529 my $res = {};
530
531 my $fh = IO::File->new ($filename, "r");
532 return $res if !$fh;
533
534 while (defined(my $line = <$fh>)) {
535 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
536 my $path = $1;
537 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
538 $res->{$1} = 1;
539 }
540 }
541 }
542
543 close($fh);
544
545 return $res;
546 }
547
548 # warning: this is slow
549 sub check_running {
550 my ($vmid) = @_;
551
552 my $active_hash = list_active_containers();
553
554 return 1 if defined($active_hash->{$vmid});
555
556 return undef;
557 }
558
559 sub get_container_disk_usage {
560 my ($vmid) = @_;
561
562 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
563
564 my $res = {
565 total => 0,
566 used => 0,
567 avail => 0,
568 };
569
570 my $parser = sub {
571 my $line = shift;
572 if (my ($fsid, $total, $used, $avail) = $line =~
573 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
574 $res = {
575 total => $total,
576 used => $used,
577 avail => $avail,
578 };
579 }
580 };
581 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
582 warn $@ if $@;
583
584 return $res;
585 }
586
587 sub vmstatus {
588 my ($opt_vmid) = @_;
589
590 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
591
592 my $active_hash = list_active_containers();
593
594 foreach my $vmid (keys %$list) {
595 my $d = $list->{$vmid};
596
597 my $running = defined($active_hash->{$vmid});
598
599 $d->{status} = $running ? 'running' : 'stopped';
600
601 my $cfspath = cfs_config_path($vmid);
602 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
603
604 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
605 $d->{name} =~ s/[\s]//g;
606
607 $d->{cpus} = 0;
608
609 my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
610 my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
611
612 if ($cfs_period_us && $cfs_quota_us) {
613 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
614 }
615
616 $d->{disk} = 0;
617 $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
618 int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
619
620 if (my $private = $conf->{'lxc.rootfs'}) {
621 if ($private =~ m!^/!) {
622 my $res = PVE::Tools::df($private, 2);
623 $d->{disk} = $res->{used};
624 $d->{maxdisk} = $res->{total};
625 } elsif ($running) {
626 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
627 my $res = get_container_disk_usage($vmid);
628 $d->{disk} = $res->{used};
629 $d->{maxdisk} = $res->{total};
630 }
631 }
632 }
633
634 $d->{mem} = 0;
635 $d->{swap} = 0;
636 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
637 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
638
639 $d->{uptime} = 0;
640 $d->{cpu} = 0;
641
642 $d->{netout} = 0;
643 $d->{netin} = 0;
644
645 $d->{diskread} = 0;
646 $d->{diskwrite} = 0;
647 }
648
649 foreach my $vmid (keys %$list) {
650 my $d = $list->{$vmid};
651 next if $d->{status} ne 'running';
652
653 $d->{uptime} = 100; # fixme:
654
655 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
656 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
657 }
658
659 return $list;
660 }
661
662
663 sub print_lxc_network {
664 my $net = shift;
665
666 die "no network bridge defined\n" if !$net->{bridge};
667
668 my $res = "bridge=$net->{bridge}";
669
670 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
671 next if !defined($net->{$k});
672 $res .= ",$k=$net->{$k}";
673 }
674
675 return $res;
676 }
677
678 sub parse_lxc_network {
679 my ($data) = @_;
680
681 my $res = {};
682
683 return $res if !$data;
684
685 foreach my $pv (split (/,/, $data)) {
686 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
687 $res->{$1} = $2;
688 } else {
689 return undef;
690 }
691 }
692
693 $res->{type} = 'veth';
694 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
695
696 return $res;
697 }
698
699 sub read_cgroup_value {
700 my ($group, $vmid, $name, $full) = @_;
701
702 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
703
704 return PVE::Tools::file_get_contents($path) if $full;
705
706 return PVE::Tools::file_read_firstline($path);
707 }
708
709 sub find_lxc_console_pids {
710
711 my $res = {};
712
713 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
714 my ($pid) = @_;
715
716 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
717 return if !$cmdline;
718
719 my @args = split(/\0/, $cmdline);
720
721 # serach for lxc-console -n <vmid>
722 return if scalar(@args) != 3;
723 return if $args[1] ne '-n';
724 return if $args[2] !~ m/^\d+$/;
725 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
726
727 my $vmid = $args[2];
728
729 push @{$res->{$vmid}}, $pid;
730 });
731
732 return $res;
733 }
734
735 my $ipv4_reverse_mask = [
736 '0.0.0.0',
737 '128.0.0.0',
738 '192.0.0.0',
739 '224.0.0.0',
740 '240.0.0.0',
741 '248.0.0.0',
742 '252.0.0.0',
743 '254.0.0.0',
744 '255.0.0.0',
745 '255.128.0.0',
746 '255.192.0.0',
747 '255.224.0.0',
748 '255.240.0.0',
749 '255.248.0.0',
750 '255.252.0.0',
751 '255.254.0.0',
752 '255.255.0.0',
753 '255.255.128.0',
754 '255.255.192.0',
755 '255.255.224.0',
756 '255.255.240.0',
757 '255.255.248.0',
758 '255.255.252.0',
759 '255.255.254.0',
760 '255.255.255.0',
761 '255.255.255.128',
762 '255.255.255.192',
763 '255.255.255.224',
764 '255.255.255.240',
765 '255.255.255.248',
766 '255.255.255.252',
767 '255.255.255.254',
768 '255.255.255.255',
769 ];
770
771 # Note: we cannot use Net:IP, because that only allows strict
772 # CIDR networks
773 sub parse_ipv4_cidr {
774 my ($cidr, $noerr) = @_;
775
776 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
777 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
778 }
779
780 return undef if $noerr;
781
782 die "unable to parse ipv4 address/mask\n";
783 }
784
785 sub lxc_conf_to_pve {
786 my ($vmid, $lxc_conf) = @_;
787
788 my $properties = json_config_properties();
789
790 my $conf = { digest => $lxc_conf->{digest} };
791
792 foreach my $k (keys %$properties) {
793
794 if ($k eq 'description') {
795 if (my $raw = $lxc_conf->{'pve.comment'}) {
796 $conf->{$k} = PVE::Tools::decode_text($raw);
797 }
798 } elsif ($k eq 'onboot') {
799 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
800 } elsif ($k eq 'startup') {
801 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
802 } elsif ($k eq 'hostname') {
803 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
804 } elsif ($k eq 'nameserver') {
805 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
806 } elsif ($k eq 'searchdomain') {
807 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
808 } elsif ($k eq 'memory') {
809 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
810 $conf->{$k} = int($value / (1024*1024));
811 }
812 } elsif ($k eq 'swap') {
813 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
814 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
815 $conf->{$k} = int(($value -$mem) / (1024*1024));
816 }
817 } elsif ($k eq 'cpus') {
818 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
819 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
820
821 if ($cfs_period_us && $cfs_quota_us) {
822 $conf->{$k} = int($cfs_quota_us/$cfs_period_us);
823 } else {
824 $conf->{$k} = 0;
825 }
826 } elsif ($k eq 'cpuunits') {
827 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
828 } elsif ($k eq 'disk') {
829 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
830 $lxc_conf->{'pve.disksize'} : 0;
831 } elsif ($k =~ m/^net\d$/) {
832 my $net = $lxc_conf->{$k};
833 next if !$net;
834 $conf->{$k} = print_lxc_network($net);
835 }
836 }
837
838 return $conf;
839 }
840
841 # verify and cleanup nameserver list (replace \0 with ' ')
842 sub verify_nameserver_list {
843 my ($nameserver_list) = @_;
844
845 my @list = ();
846 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
847 PVE::JSONSchema::pve_verify_ip($server);
848 push @list, $server;
849 }
850
851 return join(' ', @list);
852 }
853
854 sub verify_searchdomain_list {
855 my ($searchdomain_list) = @_;
856
857 my @list = ();
858 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
859 # todo: should we add checks for valid dns domains?
860 push @list, $server;
861 }
862
863 return join(' ', @list);
864 }
865
866 sub update_lxc_config {
867 my ($vmid, $conf, $running, $param, $delete) = @_;
868
869 # fixme: hotplug
870 die "unable to modify config while container is running\n" if $running;
871
872 if (defined($delete)) {
873 foreach my $opt (@$delete) {
874 if ($opt eq 'hostname' || $opt eq 'memory') {
875 die "unable to delete required option '$opt'\n";
876 } elsif ($opt eq 'swap') {
877 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
878 } elsif ($opt eq 'description') {
879 delete $conf->{'pve.comment'};
880 } elsif ($opt eq 'onboot') {
881 delete $conf->{'pve.onboot'};
882 } elsif ($opt eq 'startup') {
883 delete $conf->{'pve.startup'};
884 } elsif ($opt eq 'nameserver') {
885 delete $conf->{'pve.nameserver'};
886 } elsif ($opt eq 'searchdomain') {
887 delete $conf->{'pve.searchdomain'};
888 } elsif ($opt =~ m/^net\d$/) {
889 delete $conf->{$opt};
890 } else {
891 die "implement me"
892 }
893 }
894 }
895
896 foreach my $opt (keys %$param) {
897 my $value = $param->{$opt};
898 if ($opt eq 'hostname') {
899 $conf->{'lxc.utsname'} = $value;
900 } elsif ($opt eq 'onboot') {
901 $conf->{'pve.onboot'} = $value ? 1 : 0;
902 } elsif ($opt eq 'startup') {
903 $conf->{'pve.startup'} = $value;
904 } elsif ($opt eq 'nameserver') {
905 my $list = verify_nameserver_list($value);
906 $conf->{'pve.nameserver'} = $list;
907 } elsif ($opt eq 'searchdomain') {
908 my $list = verify_searchdomain_list($value);
909 $conf->{'pve.searchdomain'} = $list;
910 } elsif ($opt eq 'memory') {
911 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
912 } elsif ($opt eq 'swap') {
913 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
914 $mem = $param->{memory}*1024*1024 if $param->{memory};
915 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
916 } elsif ($opt eq 'cpus') {
917 if ($value > 0) {
918 my $cfs_period_us = 100000;
919 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
920 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
921 } else {
922 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
923 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
924 }
925 } elsif ($opt eq 'cpuunits') {
926 $conf->{'lxc.cgroup.cpu.shares'} = $value;
927 } elsif ($opt eq 'description') {
928 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
929 } elsif ($opt eq 'disk') {
930 $conf->{'pve.disksize'} = $value;
931 } elsif ($opt =~ m/^net(\d+)$/) {
932 my $netid = $1;
933 my $net = PVE::LXC::parse_lxc_network($value);
934 $net->{'veth.pair'} = "veth${vmid}.$netid";
935 $conf->{$opt} = $net;
936 } else {
937 die "implement me"
938 }
939 }
940 }
941
942 sub get_primary_ips {
943 my ($conf) = @_;
944
945 # return data from net0
946
947 my $net = $conf->{net0};
948 return undef if !$net;
949
950 my $ipv4 = $net->{ip};
951 $ipv4 =~ s!/\d+$!! if $ipv4;
952 my $ipv6 = $net->{ip};
953 $ipv6 =~ s!/\d+$!! if $ipv6;
954
955 return ($ipv4, $ipv6);
956 }
957
958 1;