]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
3b97d8e87ad3566bc8ebe81b2c266a69588c7ffc
[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 };
120
121 my $valid_lxc_network_keys = {
122 type => 1,
123 mtu => 1,
124 name => 1, # ifname inside container
125 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
126 hwaddr => 1,
127 };
128
129 my $valid_pve_network_keys = {
130 bridge => 1,
131 tag => 1,
132 firewall => 1,
133 ip => 1,
134 gw => 1,
135 ip6 => 1,
136 gw6 => 1,
137 };
138
139 my $lxc_array_configs = {
140 'lxc.network' => 1,
141 'lxc.mount' => 1,
142 'lxc.include' => 1,
143 };
144
145 sub write_lxc_config {
146 my ($filename, $data) = @_;
147
148 my $raw = "";
149
150 return $raw if !$data;
151
152 my $done_hash = { digest => 1};
153
154 foreach my $k (sort keys %$data) {
155 next if $k !~ m/^lxc\./;
156 $done_hash->{$k} = 1;
157 $raw .= "$k = $data->{$k}\n";
158 }
159
160 foreach my $k (sort keys %$data) {
161 next if $k !~ m/^pve\./;
162 $done_hash->{$k} = 1;
163 $raw .= "$k = $data->{$k}\n";
164 }
165
166 foreach my $k (sort keys %$data) {
167 next if $k !~ m/^net\d+$/;
168 $done_hash->{$k} = 1;
169 my $net = $data->{$k};
170 $raw .= "lxc.network.type = $net->{type}\n";
171 foreach my $subkey (sort keys %$net) {
172 next if $subkey eq 'type';
173 if ($valid_lxc_network_keys->{$subkey}) {
174 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
175 } elsif ($valid_pve_network_keys->{$subkey}) {
176 $raw .= "pve.network.$subkey = $net->{$subkey}\n";
177 } else {
178 die "found invalid network key '$subkey'";
179 }
180 }
181 }
182
183 foreach my $k (sort keys %$data) {
184 next if $done_hash->{$k};
185 die "found un-written value in config - implement this!";
186 }
187
188 return $raw;
189 }
190
191 sub parse_lxc_option {
192 my ($name, $value) = @_;
193
194 my $parser = $valid_lxc_keys->{$name};
195
196 die "inavlid key '$name'\n" if !defined($parser);
197
198 if ($parser eq '1') {
199 return $value;
200 } elsif (ref($parser)) {
201 my $res = &$parser($name, $value);
202 return $res if defined($res);
203 } else {
204 # assume regex
205 return $value if $value =~ m/^$parser$/;
206 }
207
208 die "unable to parse value '$value' for option '$name'\n";
209 }
210
211 sub parse_lxc_config {
212 my ($filename, $raw) = @_;
213
214 return undef if !defined($raw);
215
216 my $data = {
217 digest => Digest::SHA::sha1_hex($raw),
218 };
219
220 $filename =~ m|/lxc/(\d+)/config$|
221 || die "got strange filename '$filename'";
222
223 my $vmid = $1;
224
225 my $network_counter = 0;
226 my $network_list = [];
227 my $host_ifnames = {};
228
229 my $find_next_hostif_name = sub {
230 for (my $i = 0; $i < 10; $i++) {
231 my $name = "veth${vmid}.$i";
232 if (!$host_ifnames->{$name}) {
233 $host_ifnames->{$name} = 1;
234 return $name;
235 }
236 }
237
238 die "unable to find free host_ifname"; # should not happen
239 };
240
241 my $push_network = sub {
242 my ($netconf) = @_;
243 return if !$netconf;
244 push @{$network_list}, $netconf;
245 $network_counter++;
246 if (my $netname = $netconf->{'veth.pair'}) {
247 if ($netname =~ m/^veth(\d+).(\d)$/) {
248 die "wrong vmid for network interface pair\n" if $1 != $vmid;
249 my $host_ifnames->{$netname} = 1;
250 } else {
251 die "wrong network interface pair\n";
252 }
253 }
254 };
255
256 my $network;
257
258 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
259 my $line = $1;
260
261 next if $line =~ m/^\#/;
262 next if $line =~ m/^\s*$/;
263
264 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
265 my ($subkey, $value) = ($1, $2);
266 if ($subkey eq 'type') {
267 &$push_network($network);
268 $network = { type => $value };
269 } elsif ($valid_lxc_network_keys->{$subkey}) {
270 $network->{$subkey} = $value;
271 } else {
272 die "unable to parse config line: $line\n";
273 }
274 next;
275 }
276 if ($line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
277 my ($subkey, $value) = ($1, $2);
278 if ($valid_pve_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.comment)\s*=\s*(\S.*)\s*$/) {
286 my ($name, $value) = ($1, $2);
287 $data->{$name} = $value;
288 next;
289 }
290 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
291 my ($name, $value) = ($1, $2);
292
293 die "multiple definitions for $name\n" if defined($data->{$name});
294
295 $data->{$name} = parse_lxc_option($name, $value);
296 next;
297 }
298
299 die "unable to parse config line: $line\n";
300 }
301
302 &$push_network($network);
303
304 foreach my $net (@{$network_list}) {
305 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
306 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
307 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
308
309 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
310 $data->{"net$1"} = $net;
311 }
312 }
313
314 return $data;
315 }
316
317 sub config_list {
318 my $vmlist = PVE::Cluster::get_vmlist();
319 my $res = {};
320 return $res if !$vmlist || !$vmlist->{ids};
321 my $ids = $vmlist->{ids};
322
323 foreach my $vmid (keys %$ids) {
324 next if !$vmid; # skip CT0
325 my $d = $ids->{$vmid};
326 next if !$d->{node} || $d->{node} ne $nodename;
327 next if !$d->{type} || $d->{type} ne 'lxc';
328 $res->{$vmid}->{type} = 'lxc';
329 }
330 return $res;
331 }
332
333 sub cfs_config_path {
334 my ($vmid, $node) = @_;
335
336 $node = $nodename if !$node;
337 return "nodes/$node/lxc/$vmid/config";
338 }
339
340 sub config_file {
341 my ($vmid, $node) = @_;
342
343 my $cfspath = cfs_config_path($vmid, $node);
344 return "/etc/pve/$cfspath";
345 }
346
347 sub load_config {
348 my ($vmid) = @_;
349
350 my $cfspath = cfs_config_path($vmid);
351
352 my $conf = PVE::Cluster::cfs_read_file($cfspath);
353 die "container $vmid does not exists\n" if !defined($conf);
354
355 return $conf;
356 }
357
358 sub write_config {
359 my ($vmid, $conf) = @_;
360
361 my $cfspath = cfs_config_path($vmid);
362
363 PVE::Cluster::cfs_write_file($cfspath, $conf);
364 }
365
366 my $tempcounter = 0;
367 sub write_temp_config {
368 my ($vmid, $conf) = @_;
369
370 $tempcounter++;
371 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
372
373 my $raw = write_lxc_config($filename, $conf);
374
375 PVE::Tools::file_set_contents($filename, $raw);
376
377 return $filename;
378 }
379
380 sub lock_container {
381 my ($vmid, $timeout, $code, @param) = @_;
382
383 my $lockdir = "/run/lock/lxc";
384 my $lockfile = "$lockdir/pve-config-{$vmid}.lock";
385
386 File::Path::make_path($lockdir);
387
388 my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param);
389
390 die $@ if $@;
391
392 return $res;
393 }
394
395 my $confdesc = {
396 onboot => {
397 optional => 1,
398 type => 'boolean',
399 description => "Specifies whether a VM will be started during system bootup.",
400 default => 0,
401 },
402 startup => get_standard_option('pve-startup-order'),
403 cpus => {
404 optional => 1,
405 type => 'integer',
406 description => "The number of CPUs for this container (0 is unlimited).",
407 minimum => 0,
408 maximum => 128,
409 default => 0,
410 },
411 cpuunits => {
412 optional => 1,
413 type => 'integer',
414 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.",
415 minimum => 0,
416 maximum => 500000,
417 default => 1000,
418 },
419 memory => {
420 optional => 1,
421 type => 'integer',
422 description => "Amount of RAM for the VM in MB.",
423 minimum => 16,
424 default => 512,
425 },
426 swap => {
427 optional => 1,
428 type => 'integer',
429 description => "Amount of SWAP for the VM in MB.",
430 minimum => 0,
431 default => 512,
432 },
433 disk => {
434 optional => 1,
435 type => 'number',
436 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
437 minimum => 0,
438 default => 2,
439 },
440 hostname => {
441 optional => 1,
442 description => "Set a host name for the container.",
443 type => 'string',
444 maxLength => 255,
445 },
446 description => {
447 optional => 1,
448 type => 'string',
449 description => "Container description. Only used on the configuration web interface.",
450 },
451 searchdomain => {
452 optional => 1,
453 type => 'string',
454 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
455 },
456 nameserver => {
457 optional => 1,
458 type => 'string',
459 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.",
460 },
461 };
462
463 my $MAX_LXC_NETWORKS = 10;
464 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
465 $confdesc->{"net$i"} = {
466 optional => 1,
467 type => 'string', format => 'pve-lxc-network',
468 description => "Specifies network interfaces for the container.",
469 };
470 }
471
472 sub option_exists {
473 my ($name) = @_;
474
475 return defined($confdesc->{$name});
476 }
477
478 # add JSON properties for create and set function
479 sub json_config_properties {
480 my $prop = shift;
481
482 foreach my $opt (keys %$confdesc) {
483 $prop->{$opt} = $confdesc->{$opt};
484 }
485
486 return $prop;
487 }
488
489 # container status helpers
490
491 sub list_active_containers {
492
493 my $filename = "/proc/net/unix";
494
495 # similar test is used by lcxcontainers.c: list_active_containers
496 my $res = {};
497
498 my $fh = IO::File->new ($filename, "r");
499 return $res if !$fh;
500
501 while (defined(my $line = <$fh>)) {
502 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
503 my $path = $1;
504 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
505 $res->{$1} = 1;
506 }
507 }
508 }
509
510 close($fh);
511
512 return $res;
513 }
514
515 # warning: this is slow
516 sub check_running {
517 my ($vmid) = @_;
518
519 my $active_hash = list_active_containers();
520
521 return 1 if defined($active_hash->{$vmid});
522
523 return undef;
524 }
525
526 sub vmstatus {
527 my ($opt_vmid) = @_;
528
529 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
530
531 my $active_hash = list_active_containers();
532
533 foreach my $vmid (keys %$list) {
534 my $d = $list->{$vmid};
535 $d->{status} = $active_hash->{$vmid} ? 'running' : 'stopped';
536
537 my $cfspath = cfs_config_path($vmid);
538 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
539
540 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
541 $d->{name} =~ s/[\s]//g;
542
543 $d->{cpus} = 0;
544
545 my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
546 my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
547
548 if ($cfs_period_us && $cfs_quota_us) {
549 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
550 }
551
552 $d->{disk} = 0;
553 $d->{maxdisk} = 1;
554 if (my $private = $conf->{'lxc.rootfs'}) {
555 my $res = PVE::Tools::df($private, 2);
556 $d->{disk} = $res->{used};
557 $d->{maxdisk} = $res->{total};
558 }
559
560 $d->{mem} = 0;
561 $d->{swap} = 0;
562 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
563 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
564
565 $d->{uptime} = 0;
566 $d->{cpu} = 0;
567
568 $d->{netout} = 0;
569 $d->{netin} = 0;
570
571 $d->{diskread} = 0;
572 $d->{diskwrite} = 0;
573 }
574
575 foreach my $vmid (keys %$list) {
576 my $d = $list->{$vmid};
577 next if $d->{status} ne 'running';
578
579 $d->{uptime} = 100; # fixme:
580
581 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
582 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
583 }
584
585 return $list;
586 }
587
588
589 sub print_lxc_network {
590 my $net = shift;
591
592 die "no network bridge defined\n" if !$net->{bridge};
593
594 my $res = "bridge=$net->{bridge}";
595
596 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
597 next if !defined($net->{$k});
598 $res .= ",$k=$net->{$k}";
599 }
600
601 return $res;
602 }
603
604 sub parse_lxc_network {
605 my ($data) = @_;
606
607 my $res = {};
608
609 return $res if !$data;
610
611 foreach my $pv (split (/,/, $data)) {
612 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
613 $res->{$1} = $2;
614 } else {
615 return undef;
616 }
617 }
618
619 $res->{type} = 'veth';
620 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
621
622 return $res;
623 }
624
625 sub read_cgroup_value {
626 my ($group, $vmid, $name, $full) = @_;
627
628 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
629
630 return PVE::Tools::file_get_contents($path) if $full;
631
632 return PVE::Tools::file_read_firstline($path);
633 }
634
635 sub find_lxc_console_pids {
636
637 my $res = {};
638
639 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
640 my ($pid) = @_;
641
642 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
643 return if !$cmdline;
644
645 my @args = split(/\0/, $cmdline);
646
647 # serach for lxc-console -n <vmid>
648 return if scalar(@args) != 3;
649 return if $args[1] ne '-n';
650 return if $args[2] !~ m/^\d+$/;
651 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
652
653 my $vmid = $args[2];
654
655 push @{$res->{$vmid}}, $pid;
656 });
657
658 return $res;
659 }
660
661 my $ipv4_reverse_mask = [
662 '0.0.0.0',
663 '128.0.0.0',
664 '192.0.0.0',
665 '224.0.0.0',
666 '240.0.0.0',
667 '248.0.0.0',
668 '252.0.0.0',
669 '254.0.0.0',
670 '255.0.0.0',
671 '255.128.0.0',
672 '255.192.0.0',
673 '255.224.0.0',
674 '255.240.0.0',
675 '255.248.0.0',
676 '255.252.0.0',
677 '255.254.0.0',
678 '255.255.0.0',
679 '255.255.128.0',
680 '255.255.192.0',
681 '255.255.224.0',
682 '255.255.240.0',
683 '255.255.248.0',
684 '255.255.252.0',
685 '255.255.254.0',
686 '255.255.255.0',
687 '255.255.255.128',
688 '255.255.255.192',
689 '255.255.255.224',
690 '255.255.255.240',
691 '255.255.255.248',
692 '255.255.255.252',
693 '255.255.255.254',
694 '255.255.255.255',
695 ];
696
697 # Note: we cannot use Net:IP, because that only allows strict
698 # CIDR networks
699 sub parse_ipv4_cidr {
700 my ($cidr, $noerr) = @_;
701
702 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
703 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
704 }
705
706 return undef if $noerr;
707
708 die "unable to parse ipv4 address/mask\n";
709 }
710
711 sub lxc_conf_to_pve {
712 my ($vmid, $lxc_conf) = @_;
713
714 my $properties = json_config_properties();
715
716 my $conf = { digest => $lxc_conf->{digest} };
717
718 foreach my $k (keys %$properties) {
719
720 if ($k eq 'description') {
721 if (my $raw = $lxc_conf->{'pve.comment'}) {
722 $conf->{$k} = PVE::Tools::decode_text($raw);
723 }
724 } elsif ($k eq 'onboot') {
725 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
726 } elsif ($k eq 'startup') {
727 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
728 } elsif ($k eq 'hostname') {
729 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
730 } elsif ($k eq 'nameserver') {
731 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
732 } elsif ($k eq 'searchdomain') {
733 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
734 } elsif ($k eq 'memory') {
735 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
736 $conf->{$k} = int($value / (1024*1024));
737 }
738 } elsif ($k eq 'swap') {
739 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
740 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
741 $conf->{$k} = int(($value -$mem) / (1024*1024));
742 }
743 } elsif ($k eq 'cpus') {
744 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
745 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
746
747 if ($cfs_period_us && $cfs_quota_us) {
748 $conf->{$k} = int($cfs_quota_us/$cfs_period_us);
749 } else {
750 $conf->{$k} = 0;
751 }
752 } elsif ($k eq 'cpuunits') {
753 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
754 } elsif ($k =~ m/^net\d$/) {
755 my $net = $lxc_conf->{$k};
756 next if !$net;
757 $conf->{$k} = print_lxc_network($net);
758 }
759 }
760
761 return $conf;
762 }
763
764 # verify and cleanup nameserver list (replace \0 with ' ')
765 sub verify_nameserver_list {
766 my ($nameserver_list) = @_;
767
768 my @list = ();
769 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
770 PVE::JSONSchema::pve_verify_ip($server);
771 push @list, $server;
772 }
773
774 return join(' ', @list);
775 }
776
777 sub verify_searchdomain_list {
778 my ($searchdomain_list) = @_;
779
780 my @list = ();
781 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
782 # todo: should we add checks for valid dns domains?
783 push @list, $server;
784 }
785
786 return join(' ', @list);
787 }
788
789 sub update_lxc_config {
790 my ($vmid, $conf, $running, $param, $delete) = @_;
791
792 # fixme: hotplug
793 die "unable to modify config while container is running\n" if $running;
794
795 if (defined($delete)) {
796 foreach my $opt (@$delete) {
797 if ($opt eq 'hostname' || $opt eq 'memory') {
798 die "unable to delete required option '$opt'\n";
799 } elsif ($opt eq 'swap') {
800 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
801 } elsif ($opt eq 'description') {
802 delete $conf->{'pve.comment'};
803 } elsif ($opt eq 'onboot') {
804 delete $conf->{'pve.onboot'};
805 } elsif ($opt eq 'startup') {
806 delete $conf->{'pve.startup'};
807 } elsif ($opt eq 'nameserver') {
808 delete $conf->{'pve.nameserver'};
809 } elsif ($opt eq 'searchdomain') {
810 delete $conf->{'pve.searchdomain'};
811 } elsif ($opt =~ m/^net\d$/) {
812 delete $conf->{$opt};
813 } else {
814 die "implement me"
815 }
816 }
817 }
818
819 foreach my $opt (keys %$param) {
820 my $value = $param->{$opt};
821 if ($opt eq 'hostname') {
822 $conf->{'lxc.utsname'} = $value;
823 } elsif ($opt eq 'onboot') {
824 $conf->{'pve.onboot'} = $value ? 1 : 0;
825 } elsif ($opt eq 'startup') {
826 $conf->{'pve.startup'} = $value;
827 } elsif ($opt eq 'nameserver') {
828 my $list = verify_nameserver_list($value);
829 $conf->{'pve.nameserver'} = $list;
830 } elsif ($opt eq 'searchdomain') {
831 my $list = verify_searchdomain_list($value);
832 $conf->{'pve.searchdomain'} = $list;
833 } elsif ($opt eq 'memory') {
834 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
835 } elsif ($opt eq 'swap') {
836 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
837 $mem = $param->{memory}*1024*1024 if $param->{memory};
838 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
839 } elsif ($opt eq 'cpus') {
840 if ($value > 0) {
841 my $cfs_period_us = 100000;
842 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
843 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
844 } else {
845 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
846 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
847 }
848 } elsif ($opt eq 'cpuunits') {
849 $conf->{'lxc.cgroup.cpu.shares'} = $value;
850 } elsif ($opt eq 'description') {
851 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
852 } elsif ($opt =~ m/^net(\d+)$/) {
853 my $netid = $1;
854 my $net = PVE::LXC::parse_lxc_network($value);
855 $net->{'veth.pair'} = "veth${vmid}.$netid";
856 $conf->{$opt} = $net;
857 } else {
858 die "implement me"
859 }
860 }
861 }
862
863 sub get_primary_ips {
864 my ($conf) = @_;
865
866 # return data from net0
867
868 my $net = $conf->{net0};
869 return undef if !$net;
870
871 my $ipv4 = $net->{ip};
872 $ipv4 =~ s!/\d+$!! if $ipv4;
873 my $ipv6 = $net->{ip};
874 $ipv6 =~ s!/\d+$!! if $ipv6;
875
876 return ($ipv4, $ipv6);
877 }
878
879 1;