]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
nicely format 'pct list' output
[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
DM
138 foreach my $k (sort keys %$data) {
139 next if $k !~ m/^net\d+$/;
140 $done_hash->{$k} = 1;
141 my $net = $data->{$k};
142 $raw .= "lxc.network.type = $net->{type}\n";
143 foreach my $subkey (sort keys %$net) {
144 next if $subkey eq 'type';
145 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
146 }
147 }
148
149 foreach my $k (sort keys %$data) {
150 next if $done_hash->{$k};
151 die "found un-written value in config - implement this!";
152 }
153
f76a2828
DM
154 return $raw;
155}
156
822de0c3
DM
157sub parse_lxc_option {
158 my ($name, $value) = @_;
159
160 my $parser = $valid_lxc_keys->{$name};
161
162 die "inavlid key '$name'\n" if !defined($parser);
163
164 if ($parser eq '1') {
165 return $value;
166 } elsif (ref($parser)) {
167 my $res = &$parser($name, $value);
168 return $res if defined($res);
169 } else {
170 # assume regex
171 return $value if $value =~ m/^$parser$/;
172 }
173
174 die "unable to parse value '$value' for option '$name'\n";
175}
176
f76a2828
DM
177sub parse_lxc_config {
178 my ($filename, $raw) = @_;
179
180 return undef if !defined($raw);
181
182 my $data = {
183 digest => Digest::SHA::sha1_hex($raw),
184 };
185
186 $filename =~ m|/lxc/(\d+)/config$|
187 || die "got strange filename '$filename'";
188
189 my $vmid = $1;
190
191 my $network_counter = 0;
192 my $network_list = [];
193 my $host_ifnames = {};
194
195 my $find_next_hostif_name = sub {
196 for (my $i = 0; $i < 10; $i++) {
197 my $name = "veth${vmid}.$i";
198 if (!$host_ifnames->{$name}) {
199 $host_ifnames->{$name} = 1;
200 return $name;
201 }
202 }
203
204 die "unable to find free host_ifname"; # should not happen
205 };
7dfc49cc 206
f76a2828
DM
207 my $push_network = sub {
208 my ($netconf) = @_;
209 return if !$netconf;
210 push @{$network_list}, $netconf;
211 $network_counter++;
212 if (my $netname = $netconf->{'veth.pair'}) {
213 if ($netname =~ m/^veth(\d+).(\d)$/) {
214 die "wrong vmid for network interface pair\n" if $1 != $vmid;
215 my $host_ifnames->{$netname} = 1;
216 } else {
217 die "wrong network interface pair\n";
218 }
219 }
220 };
221
222 my $network;
7dfc49cc 223
f76a2828
DM
224 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
225 my $line = $1;
226
227 next if $line =~ m/^\#/;
228 next if $line =~ m/^\s*$/;
229
230 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
231 my ($subkey, $value) = ($1, $2);
232 if ($subkey eq 'type') {
233 &$push_network($network);
234 $network = { type => $value };
235 } elsif ($valid_network_keys->{$subkey}) {
236 $network->{$subkey} = $value;
237 } else {
238 die "unable to parse config line: $line\n";
239 }
7dfc49cc 240
f76a2828
DM
241 next;
242 }
243 if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
244 my ($name, $value) = ($1, $2);
245 $data->{$name} = $value;
246 next;
247 }
54664cd3 248 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
f76a2828
DM
249 my ($name, $value) = ($1, $2);
250
f76a2828
DM
251 die "multiple definitions for $name\n" if defined($data->{$name});
252
822de0c3 253 $data->{$name} = parse_lxc_option($name, $value);
f76a2828
DM
254 next;
255 }
256
257 die "unable to parse config line: $line\n";
258 }
259
260 &$push_network($network);
261
262 foreach my $net (@{$network_list}) {
263 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
264 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
265 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
266
267 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
268 $data->{"net$1"} = $net;
269 }
7dfc49cc
DM
270 }
271
272 return $data;
f76a2828
DM
273}
274
275sub config_list {
276 my $vmlist = PVE::Cluster::get_vmlist();
277 my $res = {};
278 return $res if !$vmlist || !$vmlist->{ids};
279 my $ids = $vmlist->{ids};
280
281 foreach my $vmid (keys %$ids) {
282 next if !$vmid; # skip CT0
283 my $d = $ids->{$vmid};
284 next if !$d->{node} || $d->{node} ne $nodename;
285 next if !$d->{type} || $d->{type} ne 'lxc';
286 $res->{$vmid}->{type} = 'lxc';
287 }
288 return $res;
289}
290
291sub cfs_config_path {
292 my ($vmid, $node) = @_;
293
294 $node = $nodename if !$node;
295 return "nodes/$node/lxc/$vmid/config";
296}
297
9c2d4ce9
DM
298sub config_file {
299 my ($vmid, $node) = @_;
300
301 my $cfspath = cfs_config_path($vmid, $node);
302 return "/etc/pve/$cfspath";
303}
304
f76a2828
DM
305sub load_config {
306 my ($vmid) = @_;
307
308 my $cfspath = cfs_config_path($vmid);
309
310 my $conf = PVE::Cluster::cfs_read_file($cfspath);
311 die "container $vmid does not exists\n" if !defined($conf);
312
313 return $conf;
314}
315
316sub write_config {
317 my ($vmid, $conf) = @_;
318
319 my $cfspath = cfs_config_path($vmid);
320
321 PVE::Cluster::cfs_write_file($cfspath, $conf);
322}
323
9c2d4ce9
DM
324my $tempcounter = 0;
325sub write_temp_config {
326 my ($vmid, $conf) = @_;
7dfc49cc 327
9c2d4ce9
DM
328 $tempcounter++;
329 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
330
331 my $raw = write_lxc_config($filename, $conf);
332
333 PVE::Tools::file_set_contents($filename, $raw);
7dfc49cc 334
9c2d4ce9
DM
335 return $filename;
336}
337
f76a2828
DM
338sub lock_container {
339 my ($vmid, $timeout, $code, @param) = @_;
340
341 my $lockdir = "/run/lock/lxc";
342 my $lockfile = "$lockdir/pve-config-{$vmid}.lock";
343
344 File::Path::make_path($lockdir);
345
346 my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param);
347
348 die $@ if $@;
349
350 return $res;
351}
352
353my $confdesc = {
354 onboot => {
355 optional => 1,
356 type => 'boolean',
357 description => "Specifies whether a VM will be started during system bootup.",
358 default => 0,
359 },
360 cpus => {
361 optional => 1,
362 type => 'integer',
363 description => "The number of CPUs for this container.",
364 minimum => 1,
365 default => 1,
366 },
367 cpuunits => {
368 optional => 1,
369 type => 'integer',
370 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.",
371 minimum => 0,
372 maximum => 500000,
373 default => 1000,
374 },
375 memory => {
376 optional => 1,
377 type => 'integer',
378 description => "Amount of RAM for the VM in MB.",
379 minimum => 16,
380 default => 512,
381 },
382 swap => {
383 optional => 1,
384 type => 'integer',
385 description => "Amount of SWAP for the VM in MB.",
386 minimum => 0,
387 default => 512,
388 },
389 disk => {
390 optional => 1,
391 type => 'number',
392 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
393 minimum => 0,
394 default => 2,
395 },
396 hostname => {
397 optional => 1,
398 description => "Set a host name for the container.",
399 type => 'string',
400 maxLength => 255,
401 },
402 description => {
403 optional => 1,
404 type => 'string',
405 description => "Container description. Only used on the configuration web interface.",
406 },
407 searchdomain => {
408 optional => 1,
409 type => 'string',
410 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
411 },
412 nameserver => {
413 optional => 1,
414 type => 'string',
415 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.",
416 },
ec52ac21
DM
417};
418
419my $MAX_LXC_NETWORKS = 10;
420for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
421 $confdesc->{"net$i"} = {
f76a2828
DM
422 optional => 1,
423 type => 'string', format => 'pve-lxc-network',
424 description => "Specifies network interfaces for the container.",
7dfc49cc 425 };
ec52ac21
DM
426}
427
428sub option_exists {
429 my ($name) = @_;
430
431 return defined($confdesc->{$name});
432}
f76a2828
DM
433
434# add JSON properties for create and set function
435sub json_config_properties {
436 my $prop = shift;
437
438 foreach my $opt (keys %$confdesc) {
439 $prop->{$opt} = $confdesc->{$opt};
440 }
441
442 return $prop;
443}
444
822de0c3
DM
445# container status helpers
446
447sub list_active_containers {
448
449 my $filename = "/proc/net/unix";
450
451 # similar test is used by lcxcontainers.c: list_active_containers
452 my $res = {};
453
454 my $fh = IO::File->new ($filename, "r");
455 return $res if !$fh;
456
457 while (defined(my $line = <$fh>)) {
458 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
459 my $path = $1;
460 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
461 $res->{$1} = 1;
462 }
463 }
464 }
465
466 close($fh);
467
468 return $res;
469}
f76a2828
DM
470
471sub vmstatus {
472 my ($opt_vmid) = @_;
473
474 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
475
822de0c3
DM
476 my $active_hash = list_active_containers();
477
f76a2828
DM
478 foreach my $vmid (keys %$list) {
479 next if $opt_vmid && ($vmid ne $opt_vmid);
480
481 my $d = $list->{$vmid};
822de0c3 482 $d->{status} = $active_hash->{$vmid} ? 'running' : 'stopped';
f76a2828
DM
483
484 my $cfspath = cfs_config_path($vmid);
485 if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
f76a2828
DM
486 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
487 $d->{name} =~ s/[\s]//g;
e901d418
DM
488
489 $d->{cpus} = 1;
490
491 $d->{disk} = 0;
492 $d->{maxdisk} = 1;
493
494 $d->{mem} = 0;
822de0c3
DM
495 $d->{swap} = 0;
496 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
497 ($conf->{'lxc.cgroup.memory.memsw.usage_in_bytes'}||0);
e901d418
DM
498
499 $d->{uptime} = 0;
500 $d->{cpu} = 0;
501
502 $d->{netout} = 0;
503 $d->{netin} = 0;
504
505 $d->{diskread} = 0;
506 $d->{diskwrite} = 0;
f76a2828
DM
507
508 }
509 }
510
511 return $list;
512}
513
7dfc49cc
DM
514
515sub print_lxc_network {
f76a2828
DM
516 my $net = shift;
517
7dfc49cc 518 die "no network link defined\n" if !$net->{link};
f76a2828 519
7dfc49cc
DM
520 my $res = "link=$net->{link}";
521
522 foreach my $k (qw(hwaddr mtu name ipv4 ipv4.gateway ipv6 ipv6.gateway)) {
f76a2828
DM
523 next if !defined($net->{$k});
524 $res .= ",$k=$net->{$k}";
525 }
7dfc49cc 526
f76a2828
DM
527 return $res;
528}
529
7dfc49cc
DM
530sub parse_lxc_network {
531 my ($data) = @_;
532
533 my $res = {};
534
535 return $res if !$data;
536
537 foreach my $pv (split (/,/, $data)) {
538 if ($pv =~ m/^(link|hwaddr|mtu|name|ipv4|ipv6|ipv4\.gateway|ipv6\.gateway)=(\S+)$/) {
539 $res->{$1} = $2;
540 } else {
541 return undef;
542 }
543 }
544
545 $res->{type} = 'veth';
546 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
547
548 return $res;
549}
f76a2828
DM
550
5511;