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