]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
autodetect OS distribution
[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;
12
13use Data::Dumper;
14
15cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
16
7dfc49cc
DM
17PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
18sub verify_lxc_network {
19 my ($value, $noerr) = @_;
20
21 return $value if parse_lxc_network($value);
22
23 return undef if $noerr;
24
25 die "unable to parse network setting\n";
26}
27
f76a2828
DM
28my $nodename = PVE::INotify::nodename();
29
822de0c3
DM
30sub parse_lxc_size {
31 my ($name, $value) = @_;
32
33 if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
34 my ($res, $unit) = ($1, lc($2 || 'b'));
35
36 return $res if $unit eq 'b';
37 return $res*1024 if $unit eq 'k';
38 return $res*1024*1024 if $unit eq 'm';
39 return $res*1024*1024*1024 if $unit eq 'g';
40 }
41
42 return undef;
43}
44
f76a2828 45my $valid_lxc_keys = {
822de0c3 46 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
f76a2828
DM
47 'lxc.include' => 1,
48 'lxc.rootfs' => 1,
49 'lxc.mount' => 1,
50 'lxc.utsname' => 1,
51
54664cd3 52 'lxc.id_map' => 1,
822de0c3
DM
53
54 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
55 'lxc.cgroup.memory.memsw.usage_in_bytes' => \&parse_lxc_size,
56
54664cd3
DM
57 # mount related
58 'lxc.mount' => 1,
59 'lxc.mount.entry' => 1,
60 'lxc.mount.auto' => 1,
61
62 # not used by pve
63 'lxc.tty' => 1,
64 'lxc.pts' => 1,
65 'lxc.haltsignal' => 1,
66 'lxc.rebootsignal' => 1,
67 'lxc.stopsignal' => 1,
68 'lxc.init_cmd' => 1,
69 'lxc.console' => 1,
70 'lxc.console.logfile' => 1,
71 'lxc.devttydir' => 1,
72 'lxc.autodev' => 1,
73 'lxc.kmsg' => 1,
74 'lxc.cap.drop' => 1,
75 'lxc.cap.keep' => 1,
76 'lxc.aa_profile' => 1,
77 'lxc.aa_allow_incomplete' => 1,
78 'lxc.se_context' => 1,
79 'lxc.loglevel' => 1,
80 'lxc.logfile' => 1,
81 'lxc.environment' => 1,
82
83
84 # autostart
85 'lxc.start.auto' => 1,
86 'lxc.start.delay' => 1,
87 'lxc.start.order' => 1,
88 'lxc.group' => 1,
89
90 # hooks
91 'lxc.hook.pre-start' => 1,
92 'lxc.hook.pre-mount' => 1,
93 'lxc.hook.mount' => 1,
94 'lxc.hook.autodev' => 1,
95 'lxc.hook.start' => 1,
96 'lxc.hook.post-stop' => 1,
97 'lxc.hook.clone' => 1,
98
f76a2828
DM
99 # pve related keys
100 'pve.comment' => 1,
101};
102
103my $valid_network_keys = {
104 type => 1,
105 flags => 1,
106 link => 1,
107 mtu => 1,
108 name => 1, # ifname inside container
109 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
110 hwaddr => 1,
111 ipv4 => 1,
112 'ipv4.gateway' => 1,
113 ipv6 => 1,
114 'ipv6.gateway' => 1,
115};
116
117my $lxc_array_configs = {
118 'lxc.network' => 1,
119 'lxc.mount' => 1,
120 'lxc.include' => 1,
121};
122
123sub write_lxc_config {
124 my ($filename, $data) = @_;
125
126 my $raw = "";
127
128 return $raw if !$data;
129
130 my $done_hash = { digest => 1};
7dfc49cc 131
f76a2828
DM
132 foreach my $k (sort keys %$data) {
133 next if $k !~ m/^lxc\./;
134 $done_hash->{$k} = 1;
135 $raw .= "$k = $data->{$k}\n";
136 }
7dfc49cc 137
f76a2828 138 foreach my $k (sort keys %$data) {
fff3a342
DM
139 next if $k !~ m/^pve\./;
140 $done_hash->{$k} = 1;
141 $raw .= "$k = $data->{$k}\n";
142 }
143
144 foreach my $k (sort keys %$data) {
f76a2828
DM
145 next if $k !~ m/^net\d+$/;
146 $done_hash->{$k} = 1;
147 my $net = $data->{$k};
148 $raw .= "lxc.network.type = $net->{type}\n";
149 foreach my $subkey (sort keys %$net) {
150 next if $subkey eq 'type';
151 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
152 }
153 }
154
155 foreach my $k (sort keys %$data) {
156 next if $done_hash->{$k};
157 die "found un-written value in config - implement this!";
158 }
159
f76a2828
DM
160 return $raw;
161}
162
822de0c3
DM
163sub parse_lxc_option {
164 my ($name, $value) = @_;
165
166 my $parser = $valid_lxc_keys->{$name};
167
168 die "inavlid key '$name'\n" if !defined($parser);
169
170 if ($parser eq '1') {
171 return $value;
172 } elsif (ref($parser)) {
173 my $res = &$parser($name, $value);
174 return $res if defined($res);
175 } else {
176 # assume regex
177 return $value if $value =~ m/^$parser$/;
178 }
179
180 die "unable to parse value '$value' for option '$name'\n";
181}
182
f76a2828
DM
183sub parse_lxc_config {
184 my ($filename, $raw) = @_;
185
186 return undef if !defined($raw);
187
188 my $data = {
189 digest => Digest::SHA::sha1_hex($raw),
190 };
191
192 $filename =~ m|/lxc/(\d+)/config$|
193 || die "got strange filename '$filename'";
194
195 my $vmid = $1;
196
197 my $network_counter = 0;
198 my $network_list = [];
199 my $host_ifnames = {};
200
201 my $find_next_hostif_name = sub {
202 for (my $i = 0; $i < 10; $i++) {
203 my $name = "veth${vmid}.$i";
204 if (!$host_ifnames->{$name}) {
205 $host_ifnames->{$name} = 1;
206 return $name;
207 }
208 }
209
210 die "unable to find free host_ifname"; # should not happen
211 };
7dfc49cc 212
f76a2828
DM
213 my $push_network = sub {
214 my ($netconf) = @_;
215 return if !$netconf;
216 push @{$network_list}, $netconf;
217 $network_counter++;
218 if (my $netname = $netconf->{'veth.pair'}) {
219 if ($netname =~ m/^veth(\d+).(\d)$/) {
220 die "wrong vmid for network interface pair\n" if $1 != $vmid;
221 my $host_ifnames->{$netname} = 1;
222 } else {
223 die "wrong network interface pair\n";
224 }
225 }
226 };
227
228 my $network;
7dfc49cc 229
f76a2828
DM
230 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
231 my $line = $1;
232
233 next if $line =~ m/^\#/;
234 next if $line =~ m/^\s*$/;
235
236 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
237 my ($subkey, $value) = ($1, $2);
238 if ($subkey eq 'type') {
239 &$push_network($network);
240 $network = { type => $value };
241 } elsif ($valid_network_keys->{$subkey}) {
242 $network->{$subkey} = $value;
243 } else {
244 die "unable to parse config line: $line\n";
245 }
7dfc49cc 246
f76a2828
DM
247 next;
248 }
249 if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
250 my ($name, $value) = ($1, $2);
251 $data->{$name} = $value;
252 next;
253 }
54664cd3 254 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
f76a2828
DM
255 my ($name, $value) = ($1, $2);
256
f76a2828
DM
257 die "multiple definitions for $name\n" if defined($data->{$name});
258
822de0c3 259 $data->{$name} = parse_lxc_option($name, $value);
f76a2828
DM
260 next;
261 }
262
263 die "unable to parse config line: $line\n";
264 }
265
266 &$push_network($network);
267
268 foreach my $net (@{$network_list}) {
269 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
270 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
271 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
272
273 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
274 $data->{"net$1"} = $net;
275 }
7dfc49cc
DM
276 }
277
278 return $data;
f76a2828
DM
279}
280
281sub config_list {
282 my $vmlist = PVE::Cluster::get_vmlist();
283 my $res = {};
284 return $res if !$vmlist || !$vmlist->{ids};
285 my $ids = $vmlist->{ids};
286
287 foreach my $vmid (keys %$ids) {
288 next if !$vmid; # skip CT0
289 my $d = $ids->{$vmid};
290 next if !$d->{node} || $d->{node} ne $nodename;
291 next if !$d->{type} || $d->{type} ne 'lxc';
292 $res->{$vmid}->{type} = 'lxc';
293 }
294 return $res;
295}
296
297sub cfs_config_path {
298 my ($vmid, $node) = @_;
299
300 $node = $nodename if !$node;
301 return "nodes/$node/lxc/$vmid/config";
302}
303
9c2d4ce9
DM
304sub config_file {
305 my ($vmid, $node) = @_;
306
307 my $cfspath = cfs_config_path($vmid, $node);
308 return "/etc/pve/$cfspath";
309}
310
f76a2828
DM
311sub load_config {
312 my ($vmid) = @_;
313
314 my $cfspath = cfs_config_path($vmid);
315
316 my $conf = PVE::Cluster::cfs_read_file($cfspath);
317 die "container $vmid does not exists\n" if !defined($conf);
318
319 return $conf;
320}
321
322sub write_config {
323 my ($vmid, $conf) = @_;
324
325 my $cfspath = cfs_config_path($vmid);
326
327 PVE::Cluster::cfs_write_file($cfspath, $conf);
328}
329
9c2d4ce9
DM
330my $tempcounter = 0;
331sub write_temp_config {
332 my ($vmid, $conf) = @_;
7dfc49cc 333
9c2d4ce9
DM
334 $tempcounter++;
335 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
336
337 my $raw = write_lxc_config($filename, $conf);
338
339 PVE::Tools::file_set_contents($filename, $raw);
7dfc49cc 340
9c2d4ce9
DM
341 return $filename;
342}
343
f76a2828
DM
344sub lock_container {
345 my ($vmid, $timeout, $code, @param) = @_;
346
347 my $lockdir = "/run/lock/lxc";
348 my $lockfile = "$lockdir/pve-config-{$vmid}.lock";
349
350 File::Path::make_path($lockdir);
351
352 my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param);
353
354 die $@ if $@;
355
356 return $res;
357}
358
359my $confdesc = {
360 onboot => {
361 optional => 1,
362 type => 'boolean',
363 description => "Specifies whether a VM will be started during system bootup.",
364 default => 0,
365 },
366 cpus => {
367 optional => 1,
368 type => 'integer',
369 description => "The number of CPUs for this container.",
370 minimum => 1,
371 default => 1,
372 },
373 cpuunits => {
374 optional => 1,
375 type => 'integer',
376 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.",
377 minimum => 0,
378 maximum => 500000,
379 default => 1000,
380 },
381 memory => {
382 optional => 1,
383 type => 'integer',
384 description => "Amount of RAM for the VM in MB.",
385 minimum => 16,
386 default => 512,
387 },
388 swap => {
389 optional => 1,
390 type => 'integer',
391 description => "Amount of SWAP for the VM in MB.",
392 minimum => 0,
393 default => 512,
394 },
395 disk => {
396 optional => 1,
397 type => 'number',
398 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
399 minimum => 0,
400 default => 2,
401 },
402 hostname => {
403 optional => 1,
404 description => "Set a host name for the container.",
405 type => 'string',
406 maxLength => 255,
407 },
408 description => {
409 optional => 1,
410 type => 'string',
411 description => "Container description. Only used on the configuration web interface.",
412 },
413 searchdomain => {
414 optional => 1,
415 type => 'string',
416 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
417 },
418 nameserver => {
419 optional => 1,
420 type => 'string',
421 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.",
422 },
ec52ac21
DM
423};
424
425my $MAX_LXC_NETWORKS = 10;
426for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
427 $confdesc->{"net$i"} = {
f76a2828
DM
428 optional => 1,
429 type => 'string', format => 'pve-lxc-network',
430 description => "Specifies network interfaces for the container.",
7dfc49cc 431 };
ec52ac21
DM
432}
433
434sub option_exists {
435 my ($name) = @_;
436
437 return defined($confdesc->{$name});
438}
f76a2828
DM
439
440# add JSON properties for create and set function
441sub json_config_properties {
442 my $prop = shift;
443
444 foreach my $opt (keys %$confdesc) {
445 $prop->{$opt} = $confdesc->{$opt};
446 }
447
448 return $prop;
449}
450
822de0c3
DM
451# container status helpers
452
453sub list_active_containers {
454
455 my $filename = "/proc/net/unix";
456
457 # similar test is used by lcxcontainers.c: list_active_containers
458 my $res = {};
459
460 my $fh = IO::File->new ($filename, "r");
461 return $res if !$fh;
462
463 while (defined(my $line = <$fh>)) {
464 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
465 my $path = $1;
466 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
467 $res->{$1} = 1;
468 }
469 }
470 }
471
472 close($fh);
473
474 return $res;
475}
f76a2828 476
5c752bbf
DM
477# warning: this is slow
478sub check_running {
479 my ($vmid) = @_;
480
481 my $active_hash = list_active_containers();
482
483 return 1 if defined($active_hash->{$vmid});
484
485 return undef;
486}
487
f76a2828
DM
488sub vmstatus {
489 my ($opt_vmid) = @_;
490
491 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
492
822de0c3
DM
493 my $active_hash = list_active_containers();
494
f76a2828 495 foreach my $vmid (keys %$list) {
f76a2828 496 my $d = $list->{$vmid};
822de0c3 497 $d->{status} = $active_hash->{$vmid} ? 'running' : 'stopped';
f76a2828
DM
498
499 my $cfspath = cfs_config_path($vmid);
238a56cb
DM
500 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
501
502 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
503 $d->{name} =~ s/[\s]//g;
e901d418 504
238a56cb
DM
505 $d->{cpus} = 1; # fixme:
506
507 $d->{disk} = 0;
508 $d->{maxdisk} = 1;
509 if (my $private = $conf->{'lxc.rootfs'}) {
510 my $res = PVE::Tools::df($private, 2);
511 $d->{disk} = $res->{used};
512 $d->{maxdisk} = $res->{total};
513 }
514
515 $d->{mem} = 0;
516 $d->{swap} = 0;
517 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
518 ($conf->{'lxc.cgroup.memory.memsw.usage_in_bytes'}||0);
e901d418 519
238a56cb
DM
520 $d->{uptime} = 0;
521 $d->{cpu} = 0;
e901d418 522
238a56cb
DM
523 $d->{netout} = 0;
524 $d->{netin} = 0;
f76a2828 525
238a56cb
DM
526 $d->{diskread} = 0;
527 $d->{diskwrite} = 0;
f76a2828 528 }
238a56cb
DM
529
530 foreach my $vmid (keys %$list) {
531 my $d = $list->{$vmid};
532 next if $d->{status} ne 'running';
f76a2828 533
22a77285
DM
534 $d->{uptime} = 100; # fixme:
535
238a56cb
DM
536 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
537 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
538 }
539
f76a2828
DM
540 return $list;
541}
542
7dfc49cc
DM
543
544sub print_lxc_network {
f76a2828
DM
545 my $net = shift;
546
7dfc49cc 547 die "no network link defined\n" if !$net->{link};
f76a2828 548
7dfc49cc
DM
549 my $res = "link=$net->{link}";
550
551 foreach my $k (qw(hwaddr mtu name ipv4 ipv4.gateway ipv6 ipv6.gateway)) {
f76a2828
DM
552 next if !defined($net->{$k});
553 $res .= ",$k=$net->{$k}";
554 }
7dfc49cc 555
f76a2828
DM
556 return $res;
557}
558
7dfc49cc
DM
559sub parse_lxc_network {
560 my ($data) = @_;
561
562 my $res = {};
563
564 return $res if !$data;
565
566 foreach my $pv (split (/,/, $data)) {
567 if ($pv =~ m/^(link|hwaddr|mtu|name|ipv4|ipv6|ipv4\.gateway|ipv6\.gateway)=(\S+)$/) {
568 $res->{$1} = $2;
569 } else {
570 return undef;
571 }
572 }
573
574 $res->{type} = 'veth';
575 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
576
577 return $res;
578}
f76a2828 579
238a56cb
DM
580sub read_cgroup_value {
581 my ($group, $vmid, $name, $full) = @_;
582
583 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
584
585 return PVE::Tools::file_get_contents($path) if $full;
586
587 return PVE::Tools::file_read_firstline($path);
588}
589
52f1d76b
DM
590sub find_lxc_console_pids {
591
592 my $res = {};
593
594 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
595 my ($pid) = @_;
596
597 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
598 return if !$cmdline;
599
600 my @args = split(/\0/, $cmdline);
601
602 # serach for lxc-console -n <vmid>
603 return if scalar(@args) != 3;
604 return if $args[1] ne '-n';
605 return if $args[2] !~ m/^\d+$/;
606 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
607
608 my $vmid = $args[2];
609
610 push @{$res->{$vmid}}, $pid;
611 });
612
613 return $res;
614}
615
238a56cb 616
f76a2828 6171;