]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
PVE::LXCCreate, use our own class instead of running extenal lxc-create
[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,
5b4657d0 143 'lxc.id_map' => 1,
f76a2828
DM
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};
7dfc49cc 154
f76a2828
DM
155 foreach my $k (sort keys %$data) {
156 next if $k !~ m/^lxc\./;
157 $done_hash->{$k} = 1;
5b4657d0
DM
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 }
f76a2828 166 }
7dfc49cc 167
f76a2828 168 foreach my $k (sort keys %$data) {
fff3a342
DM
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) {
f76a2828
DM
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';
93285df8
DM
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 }
f76a2828
DM
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
f76a2828
DM
196 return $raw;
197}
198
822de0c3
DM
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
f76a2828
DM
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 };
7dfc49cc 248
f76a2828
DM
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;
7dfc49cc 265
f76a2828
DM
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 };
93285df8
DM
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}) {
f76a2828
DM
287 $network->{$subkey} = $value;
288 } else {
289 die "unable to parse config line: $line\n";
290 }
f76a2828
DM
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 }
54664cd3 298 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
f76a2828
DM
299 my ($name, $value) = ($1, $2);
300
5b4657d0
DM
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
f76a2828
DM
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 }
7dfc49cc
DM
325 }
326
327 return $data;
f76a2828
DM
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
9c2d4ce9
DM
353sub config_file {
354 my ($vmid, $node) = @_;
355
356 my $cfspath = cfs_config_path($vmid, $node);
357 return "/etc/pve/$cfspath";
358}
359
f76a2828
DM
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
5b4657d0
DM
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
f76a2828
DM
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
9c2d4ce9
DM
398my $tempcounter = 0;
399sub write_temp_config {
400 my ($vmid, $conf) = @_;
7dfc49cc 401
9c2d4ce9
DM
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);
7dfc49cc 408
9c2d4ce9
DM
409 return $filename;
410}
411
f76a2828
DM
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 },
a3249355 434 startup => get_standard_option('pve-startup-order'),
f76a2828
DM
435 cpus => {
436 optional => 1,
437 type => 'integer',
44da0641
DM
438 description => "The number of CPUs for this container (0 is unlimited).",
439 minimum => 0,
440 maximum => 128,
441 default => 0,
f76a2828
DM
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 },
ec52ac21
DM
493};
494
495my $MAX_LXC_NETWORKS = 10;
496for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
497 $confdesc->{"net$i"} = {
f76a2828
DM
498 optional => 1,
499 type => 'string', format => 'pve-lxc-network',
500 description => "Specifies network interfaces for the container.",
7dfc49cc 501 };
ec52ac21
DM
502}
503
504sub option_exists {
505 my ($name) = @_;
506
507 return defined($confdesc->{$name});
508}
f76a2828
DM
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
822de0c3
DM
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}
f76a2828 546
5c752bbf
DM
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
f76a2828
DM
558sub vmstatus {
559 my ($opt_vmid) = @_;
560
561 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
562
822de0c3
DM
563 my $active_hash = list_active_containers();
564
f76a2828 565 foreach my $vmid (keys %$list) {
f76a2828 566 my $d = $list->{$vmid};
822de0c3 567 $d->{status} = $active_hash->{$vmid} ? 'running' : 'stopped';
f76a2828
DM
568
569 my $cfspath = cfs_config_path($vmid);
238a56cb
DM
570 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
571
572 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
573 $d->{name} =~ s/[\s]//g;
e901d418 574
44da0641
DM
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 }
238a56cb
DM
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) +
44da0641 595 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
e901d418 596
238a56cb
DM
597 $d->{uptime} = 0;
598 $d->{cpu} = 0;
e901d418 599
238a56cb
DM
600 $d->{netout} = 0;
601 $d->{netin} = 0;
f76a2828 602
238a56cb
DM
603 $d->{diskread} = 0;
604 $d->{diskwrite} = 0;
f76a2828 605 }
238a56cb
DM
606
607 foreach my $vmid (keys %$list) {
608 my $d = $list->{$vmid};
609 next if $d->{status} ne 'running';
f76a2828 610
22a77285
DM
611 $d->{uptime} = 100; # fixme:
612
238a56cb
DM
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
f76a2828
DM
617 return $list;
618}
619
7dfc49cc
DM
620
621sub print_lxc_network {
f76a2828
DM
622 my $net = shift;
623
8a04b6c7 624 die "no network bridge defined\n" if !$net->{bridge};
f76a2828 625
8a04b6c7 626 my $res = "bridge=$net->{bridge}";
7dfc49cc 627
2b1fc2ea 628 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
629 next if !defined($net->{$k});
630 $res .= ",$k=$net->{$k}";
631 }
7dfc49cc 632
f76a2828
DM
633 return $res;
634}
635
7dfc49cc
DM
636sub parse_lxc_network {
637 my ($data) = @_;
638
639 my $res = {};
640
641 return $res if !$data;
642
643 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 644 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
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}
f76a2828 656
238a56cb
DM
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
52f1d76b
DM
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
55fa4e09
DM
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}
93285df8 742
b80dd50a
DM
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 }
a99b3509
DM
756 } elsif ($k eq 'onboot') {
757 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
a3249355
DM
758 } elsif ($k eq 'startup') {
759 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
b80dd50a
DM
760 } elsif ($k eq 'hostname') {
761 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
ffa1d001
DM
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'};
b80dd50a
DM
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
117636e5
DM
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
93285df8
DM
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') {
44da0641 832 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
93285df8
DM
833 } elsif ($opt eq 'description') {
834 delete $conf->{'pve.comment'};
a99b3509
DM
835 } elsif ($opt eq 'onboot') {
836 delete $conf->{'pve.onboot'};
a3249355
DM
837 } elsif ($opt eq 'startup') {
838 delete $conf->{'pve.startup'};
ffa1d001
DM
839 } elsif ($opt eq 'nameserver') {
840 delete $conf->{'pve.nameserver'};
841 } elsif ($opt eq 'searchdomain') {
842 delete $conf->{'pve.searchdomain'};
93285df8
DM
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;
a99b3509
DM
855 } elsif ($opt eq 'onboot') {
856 $conf->{'pve.onboot'} = $value ? 1 : 0;
a3249355
DM
857 } elsif ($opt eq 'startup') {
858 $conf->{'pve.startup'} = $value;
ffa1d001 859 } elsif ($opt eq 'nameserver') {
117636e5 860 my $list = verify_nameserver_list($value);
c325b32f 861 $conf->{'pve.nameserver'} = $list;
ffa1d001 862 } elsif ($opt eq 'searchdomain') {
117636e5 863 my $list = verify_searchdomain_list($value);
c325b32f 864 $conf->{'pve.searchdomain'} = $list;
93285df8
DM
865 } elsif ($opt eq 'memory') {
866 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
867 } elsif ($opt eq 'swap') {
44da0641
DM
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 }
b80dd50a
DM
880 } elsif ($opt eq 'cpuunits') {
881 $conf->{'lxc.cgroup.cpu.shares'} = $value;
93285df8
DM
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}
c325b32f
DM
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}
238a56cb 910
f76a2828 9111;