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