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