]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
bump version to 0.4-1
[pve-container.git] / src / PVE / LXC.pm
CommitLineData
f76a2828
DM
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;
a3249355 12use PVE::JSONSchema qw(get_standard_option);
55fa4e09 13use PVE::Tools qw($IPV6RE $IPV4RE);
f76a2828
DM
14
15use Data::Dumper;
16
17cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
18
7dfc49cc
DM
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
f76a2828
DM
30my $nodename = PVE::INotify::nodename();
31
822de0c3
DM
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
f76a2828 47my $valid_lxc_keys = {
822de0c3 48 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
f76a2828
DM
49 'lxc.include' => 1,
50 'lxc.rootfs' => 1,
51 'lxc.mount' => 1,
52 'lxc.utsname' => 1,
53
54664cd3 54 'lxc.id_map' => 1,
822de0c3
DM
55
56 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
44da0641 57 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
b80dd50a
DM
58 'lxc.cgroup.cpu.cfs_period_us' => '\d+',
59 'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
60 'lxc.cgroup.cpu.shares' => '\d+',
822de0c3 61
54664cd3
DM
62 # mount related
63 'lxc.mount' => 1,
64 'lxc.mount.entry' => 1,
65 'lxc.mount.auto' => 1,
66
67 # not used by pve
b80dd50a 68 'lxc.tty' => '\d+',
54664cd3
DM
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
f76a2828 104 # pve related keys
117636e5
DM
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 },
a99b3509 113 'pve.onboot' => '(0|1)',
a3249355
DM
114 'pve.startup' => sub {
115 my ($name, $value) = @_;
116 return PVE::JSONSchema::pve_verify_startup_order($value);
117 },
f76a2828
DM
118 'pve.comment' => 1,
119};
120
93285df8 121my $valid_lxc_network_keys = {
f76a2828 122 type => 1,
f76a2828
DM
123 mtu => 1,
124 name => 1, # ifname inside container
125 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
126 hwaddr => 1,
93285df8
DM
127};
128
129my $valid_pve_network_keys = {
8a04b6c7 130 bridge => 1,
2b1fc2ea
DM
131 tag => 1,
132 firewall => 1,
93285df8
DM
133 ip => 1,
134 gw => 1,
135 ip6 => 1,
136 gw6 => 1,
f76a2828
DM
137};
138
139my $lxc_array_configs = {
140 'lxc.network' => 1,
141 'lxc.mount' => 1,
142 'lxc.include' => 1,
143};
144
145sub write_lxc_config {
146 my ($filename, $data) = @_;
147
148 my $raw = "";
149
150 return $raw if !$data;
151
152 my $done_hash = { digest => 1};
7dfc49cc 153
f76a2828
DM
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 }
7dfc49cc 159
f76a2828 160 foreach my $k (sort keys %$data) {
fff3a342
DM
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) {
f76a2828
DM
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';
93285df8
DM
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 }
f76a2828
DM
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
f76a2828
DM
188 return $raw;
189}
190
822de0c3
DM
191sub 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
f76a2828
DM
211sub 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 };
7dfc49cc 240
f76a2828
DM
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;
7dfc49cc 257
f76a2828
DM
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 };
93285df8
DM
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}) {
f76a2828
DM
279 $network->{$subkey} = $value;
280 } else {
281 die "unable to parse config line: $line\n";
282 }
f76a2828
DM
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 }
54664cd3 290 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
f76a2828
DM
291 my ($name, $value) = ($1, $2);
292
f76a2828
DM
293 die "multiple definitions for $name\n" if defined($data->{$name});
294
822de0c3 295 $data->{$name} = parse_lxc_option($name, $value);
f76a2828
DM
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 }
7dfc49cc
DM
312 }
313
314 return $data;
f76a2828
DM
315}
316
317sub 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
333sub cfs_config_path {
334 my ($vmid, $node) = @_;
335
336 $node = $nodename if !$node;
337 return "nodes/$node/lxc/$vmid/config";
338}
339
9c2d4ce9
DM
340sub config_file {
341 my ($vmid, $node) = @_;
342
343 my $cfspath = cfs_config_path($vmid, $node);
344 return "/etc/pve/$cfspath";
345}
346
f76a2828
DM
347sub 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
358sub 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
9c2d4ce9
DM
366my $tempcounter = 0;
367sub write_temp_config {
368 my ($vmid, $conf) = @_;
7dfc49cc 369
9c2d4ce9
DM
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);
7dfc49cc 376
9c2d4ce9
DM
377 return $filename;
378}
379
f76a2828
DM
380sub 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
395my $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 },
a3249355 402 startup => get_standard_option('pve-startup-order'),
f76a2828
DM
403 cpus => {
404 optional => 1,
405 type => 'integer',
44da0641
DM
406 description => "The number of CPUs for this container (0 is unlimited).",
407 minimum => 0,
408 maximum => 128,
409 default => 0,
f76a2828
DM
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 },
ec52ac21
DM
461};
462
463my $MAX_LXC_NETWORKS = 10;
464for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
465 $confdesc->{"net$i"} = {
f76a2828
DM
466 optional => 1,
467 type => 'string', format => 'pve-lxc-network',
468 description => "Specifies network interfaces for the container.",
7dfc49cc 469 };
ec52ac21
DM
470}
471
472sub option_exists {
473 my ($name) = @_;
474
475 return defined($confdesc->{$name});
476}
f76a2828
DM
477
478# add JSON properties for create and set function
479sub 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
822de0c3
DM
489# container status helpers
490
491sub 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}
f76a2828 514
5c752bbf
DM
515# warning: this is slow
516sub 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
f76a2828
DM
526sub vmstatus {
527 my ($opt_vmid) = @_;
528
529 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
530
822de0c3
DM
531 my $active_hash = list_active_containers();
532
f76a2828 533 foreach my $vmid (keys %$list) {
f76a2828 534 my $d = $list->{$vmid};
822de0c3 535 $d->{status} = $active_hash->{$vmid} ? 'running' : 'stopped';
f76a2828
DM
536
537 my $cfspath = cfs_config_path($vmid);
238a56cb
DM
538 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
539
540 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
541 $d->{name} =~ s/[\s]//g;
e901d418 542
44da0641
DM
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 }
238a56cb
DM
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) +
44da0641 563 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
e901d418 564
238a56cb
DM
565 $d->{uptime} = 0;
566 $d->{cpu} = 0;
e901d418 567
238a56cb
DM
568 $d->{netout} = 0;
569 $d->{netin} = 0;
f76a2828 570
238a56cb
DM
571 $d->{diskread} = 0;
572 $d->{diskwrite} = 0;
f76a2828 573 }
238a56cb
DM
574
575 foreach my $vmid (keys %$list) {
576 my $d = $list->{$vmid};
577 next if $d->{status} ne 'running';
f76a2828 578
22a77285
DM
579 $d->{uptime} = 100; # fixme:
580
238a56cb
DM
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
f76a2828
DM
585 return $list;
586}
587
7dfc49cc
DM
588
589sub print_lxc_network {
f76a2828
DM
590 my $net = shift;
591
8a04b6c7 592 die "no network bridge defined\n" if !$net->{bridge};
f76a2828 593
8a04b6c7 594 my $res = "bridge=$net->{bridge}";
7dfc49cc 595
2b1fc2ea 596 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
597 next if !defined($net->{$k});
598 $res .= ",$k=$net->{$k}";
599 }
7dfc49cc 600
f76a2828
DM
601 return $res;
602}
603
7dfc49cc
DM
604sub parse_lxc_network {
605 my ($data) = @_;
606
607 my $res = {};
608
609 return $res if !$data;
610
611 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 612 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
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}
f76a2828 624
238a56cb
DM
625sub 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
52f1d76b
DM
635sub 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
55fa4e09
DM
661my $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
699sub 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}
93285df8 710
b80dd50a
DM
711sub 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 }
a99b3509
DM
724 } elsif ($k eq 'onboot') {
725 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
a3249355
DM
726 } elsif ($k eq 'startup') {
727 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
b80dd50a
DM
728 } elsif ($k eq 'hostname') {
729 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
ffa1d001
DM
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'};
b80dd50a
DM
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
117636e5
DM
764# verify and cleanup nameserver list (replace \0 with ' ')
765sub 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
777sub 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
93285df8
DM
789sub 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') {
44da0641 800 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
93285df8
DM
801 } elsif ($opt eq 'description') {
802 delete $conf->{'pve.comment'};
a99b3509
DM
803 } elsif ($opt eq 'onboot') {
804 delete $conf->{'pve.onboot'};
a3249355
DM
805 } elsif ($opt eq 'startup') {
806 delete $conf->{'pve.startup'};
ffa1d001
DM
807 } elsif ($opt eq 'nameserver') {
808 delete $conf->{'pve.nameserver'};
809 } elsif ($opt eq 'searchdomain') {
810 delete $conf->{'pve.searchdomain'};
93285df8
DM
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;
a99b3509
DM
823 } elsif ($opt eq 'onboot') {
824 $conf->{'pve.onboot'} = $value ? 1 : 0;
a3249355
DM
825 } elsif ($opt eq 'startup') {
826 $conf->{'pve.startup'} = $value;
ffa1d001 827 } elsif ($opt eq 'nameserver') {
117636e5 828 my $list = verify_nameserver_list($value);
c325b32f 829 $conf->{'pve.nameserver'} = $list;
ffa1d001 830 } elsif ($opt eq 'searchdomain') {
117636e5 831 my $list = verify_searchdomain_list($value);
c325b32f 832 $conf->{'pve.searchdomain'} = $list;
93285df8
DM
833 } elsif ($opt eq 'memory') {
834 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
835 } elsif ($opt eq 'swap') {
44da0641
DM
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 }
b80dd50a
DM
848 } elsif ($opt eq 'cpuunits') {
849 $conf->{'lxc.cgroup.cpu.shares'} = $value;
93285df8
DM
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}
c325b32f
DM
862
863sub 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}
238a56cb 878
f76a2828 8791;