]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
improve lxc config parser
[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
30my $valid_lxc_keys = {
31 'lxc.arch' => 1,
32 'lxc.include' => 1,
33 'lxc.rootfs' => 1,
34 'lxc.mount' => 1,
35 'lxc.utsname' => 1,
36
54664cd3 37 'lxc.id_map' => 1,
f76a2828
DM
38 'lxc.cgroup.memory.limit_in_bytes' => 1,
39
54664cd3
DM
40 # mount related
41 'lxc.mount' => 1,
42 'lxc.mount.entry' => 1,
43 'lxc.mount.auto' => 1,
44
45 # not used by pve
46 'lxc.tty' => 1,
47 'lxc.pts' => 1,
48 'lxc.haltsignal' => 1,
49 'lxc.rebootsignal' => 1,
50 'lxc.stopsignal' => 1,
51 'lxc.init_cmd' => 1,
52 'lxc.console' => 1,
53 'lxc.console.logfile' => 1,
54 'lxc.devttydir' => 1,
55 'lxc.autodev' => 1,
56 'lxc.kmsg' => 1,
57 'lxc.cap.drop' => 1,
58 'lxc.cap.keep' => 1,
59 'lxc.aa_profile' => 1,
60 'lxc.aa_allow_incomplete' => 1,
61 'lxc.se_context' => 1,
62 'lxc.loglevel' => 1,
63 'lxc.logfile' => 1,
64 'lxc.environment' => 1,
65
66
67 # autostart
68 'lxc.start.auto' => 1,
69 'lxc.start.delay' => 1,
70 'lxc.start.order' => 1,
71 'lxc.group' => 1,
72
73 # hooks
74 'lxc.hook.pre-start' => 1,
75 'lxc.hook.pre-mount' => 1,
76 'lxc.hook.mount' => 1,
77 'lxc.hook.autodev' => 1,
78 'lxc.hook.start' => 1,
79 'lxc.hook.post-stop' => 1,
80 'lxc.hook.clone' => 1,
81
f76a2828
DM
82 # pve related keys
83 'pve.comment' => 1,
84};
85
86my $valid_network_keys = {
87 type => 1,
88 flags => 1,
89 link => 1,
90 mtu => 1,
91 name => 1, # ifname inside container
92 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
93 hwaddr => 1,
94 ipv4 => 1,
95 'ipv4.gateway' => 1,
96 ipv6 => 1,
97 'ipv6.gateway' => 1,
98};
99
100my $lxc_array_configs = {
101 'lxc.network' => 1,
102 'lxc.mount' => 1,
103 'lxc.include' => 1,
104};
105
106sub write_lxc_config {
107 my ($filename, $data) = @_;
108
109 my $raw = "";
110
111 return $raw if !$data;
112
113 my $done_hash = { digest => 1};
7dfc49cc 114
f76a2828
DM
115 foreach my $k (sort keys %$data) {
116 next if $k !~ m/^lxc\./;
117 $done_hash->{$k} = 1;
118 $raw .= "$k = $data->{$k}\n";
119 }
7dfc49cc 120
f76a2828
DM
121 foreach my $k (sort keys %$data) {
122 next if $k !~ m/^net\d+$/;
123 $done_hash->{$k} = 1;
124 my $net = $data->{$k};
125 $raw .= "lxc.network.type = $net->{type}\n";
126 foreach my $subkey (sort keys %$net) {
127 next if $subkey eq 'type';
128 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
129 }
130 }
131
132 foreach my $k (sort keys %$data) {
133 next if $done_hash->{$k};
134 die "found un-written value in config - implement this!";
135 }
136
f76a2828
DM
137 return $raw;
138}
139
140sub parse_lxc_config {
141 my ($filename, $raw) = @_;
142
143 return undef if !defined($raw);
144
145 my $data = {
146 digest => Digest::SHA::sha1_hex($raw),
147 };
148
149 $filename =~ m|/lxc/(\d+)/config$|
150 || die "got strange filename '$filename'";
151
152 my $vmid = $1;
153
154 my $network_counter = 0;
155 my $network_list = [];
156 my $host_ifnames = {};
157
158 my $find_next_hostif_name = sub {
159 for (my $i = 0; $i < 10; $i++) {
160 my $name = "veth${vmid}.$i";
161 if (!$host_ifnames->{$name}) {
162 $host_ifnames->{$name} = 1;
163 return $name;
164 }
165 }
166
167 die "unable to find free host_ifname"; # should not happen
168 };
7dfc49cc 169
f76a2828
DM
170 my $push_network = sub {
171 my ($netconf) = @_;
172 return if !$netconf;
173 push @{$network_list}, $netconf;
174 $network_counter++;
175 if (my $netname = $netconf->{'veth.pair'}) {
176 if ($netname =~ m/^veth(\d+).(\d)$/) {
177 die "wrong vmid for network interface pair\n" if $1 != $vmid;
178 my $host_ifnames->{$netname} = 1;
179 } else {
180 die "wrong network interface pair\n";
181 }
182 }
183 };
184
185 my $network;
7dfc49cc 186
f76a2828
DM
187 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
188 my $line = $1;
189
190 next if $line =~ m/^\#/;
191 next if $line =~ m/^\s*$/;
192
193 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
194 my ($subkey, $value) = ($1, $2);
195 if ($subkey eq 'type') {
196 &$push_network($network);
197 $network = { type => $value };
198 } elsif ($valid_network_keys->{$subkey}) {
199 $network->{$subkey} = $value;
200 } else {
201 die "unable to parse config line: $line\n";
202 }
7dfc49cc 203
f76a2828
DM
204 next;
205 }
206 if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
207 my ($name, $value) = ($1, $2);
208 $data->{$name} = $value;
209 next;
210 }
54664cd3 211 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
f76a2828
DM
212 my ($name, $value) = ($1, $2);
213
7dfc49cc 214 die "inavlid key '$name'\n" if !$valid_lxc_keys->{$name};
f76a2828
DM
215
216 die "multiple definitions for $name\n" if defined($data->{$name});
217
218 $data->{$name} = $value;
219 next;
220 }
221
222 die "unable to parse config line: $line\n";
223 }
224
225 &$push_network($network);
226
227 foreach my $net (@{$network_list}) {
228 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
229 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
230 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
231
232 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
233 $data->{"net$1"} = $net;
234 }
7dfc49cc
DM
235 }
236
237 return $data;
f76a2828
DM
238}
239
240sub config_list {
241 my $vmlist = PVE::Cluster::get_vmlist();
242 my $res = {};
243 return $res if !$vmlist || !$vmlist->{ids};
244 my $ids = $vmlist->{ids};
245
246 foreach my $vmid (keys %$ids) {
247 next if !$vmid; # skip CT0
248 my $d = $ids->{$vmid};
249 next if !$d->{node} || $d->{node} ne $nodename;
250 next if !$d->{type} || $d->{type} ne 'lxc';
251 $res->{$vmid}->{type} = 'lxc';
252 }
253 return $res;
254}
255
256sub cfs_config_path {
257 my ($vmid, $node) = @_;
258
259 $node = $nodename if !$node;
260 return "nodes/$node/lxc/$vmid/config";
261}
262
9c2d4ce9
DM
263sub config_file {
264 my ($vmid, $node) = @_;
265
266 my $cfspath = cfs_config_path($vmid, $node);
267 return "/etc/pve/$cfspath";
268}
269
f76a2828
DM
270sub load_config {
271 my ($vmid) = @_;
272
273 my $cfspath = cfs_config_path($vmid);
274
275 my $conf = PVE::Cluster::cfs_read_file($cfspath);
276 die "container $vmid does not exists\n" if !defined($conf);
277
278 return $conf;
279}
280
281sub write_config {
282 my ($vmid, $conf) = @_;
283
284 my $cfspath = cfs_config_path($vmid);
285
286 PVE::Cluster::cfs_write_file($cfspath, $conf);
287}
288
9c2d4ce9
DM
289my $tempcounter = 0;
290sub write_temp_config {
291 my ($vmid, $conf) = @_;
7dfc49cc 292
9c2d4ce9
DM
293 $tempcounter++;
294 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
295
296 my $raw = write_lxc_config($filename, $conf);
297
298 PVE::Tools::file_set_contents($filename, $raw);
7dfc49cc 299
9c2d4ce9
DM
300 return $filename;
301}
302
f76a2828
DM
303sub lock_container {
304 my ($vmid, $timeout, $code, @param) = @_;
305
306 my $lockdir = "/run/lock/lxc";
307 my $lockfile = "$lockdir/pve-config-{$vmid}.lock";
308
309 File::Path::make_path($lockdir);
310
311 my $res = PVE::Tools::lock_file($lockfile, $timeout, $code, @param);
312
313 die $@ if $@;
314
315 return $res;
316}
317
318my $confdesc = {
319 onboot => {
320 optional => 1,
321 type => 'boolean',
322 description => "Specifies whether a VM will be started during system bootup.",
323 default => 0,
324 },
325 cpus => {
326 optional => 1,
327 type => 'integer',
328 description => "The number of CPUs for this container.",
329 minimum => 1,
330 default => 1,
331 },
332 cpuunits => {
333 optional => 1,
334 type => 'integer',
335 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.",
336 minimum => 0,
337 maximum => 500000,
338 default => 1000,
339 },
340 memory => {
341 optional => 1,
342 type => 'integer',
343 description => "Amount of RAM for the VM in MB.",
344 minimum => 16,
345 default => 512,
346 },
347 swap => {
348 optional => 1,
349 type => 'integer',
350 description => "Amount of SWAP for the VM in MB.",
351 minimum => 0,
352 default => 512,
353 },
354 disk => {
355 optional => 1,
356 type => 'number',
357 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
358 minimum => 0,
359 default => 2,
360 },
361 hostname => {
362 optional => 1,
363 description => "Set a host name for the container.",
364 type => 'string',
365 maxLength => 255,
366 },
367 description => {
368 optional => 1,
369 type => 'string',
370 description => "Container description. Only used on the configuration web interface.",
371 },
372 searchdomain => {
373 optional => 1,
374 type => 'string',
375 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
376 },
377 nameserver => {
378 optional => 1,
379 type => 'string',
380 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.",
381 },
ec52ac21
DM
382};
383
384my $MAX_LXC_NETWORKS = 10;
385for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
386 $confdesc->{"net$i"} = {
f76a2828
DM
387 optional => 1,
388 type => 'string', format => 'pve-lxc-network',
389 description => "Specifies network interfaces for the container.",
7dfc49cc 390 };
ec52ac21
DM
391}
392
393sub option_exists {
394 my ($name) = @_;
395
396 return defined($confdesc->{$name});
397}
f76a2828
DM
398
399# add JSON properties for create and set function
400sub json_config_properties {
401 my $prop = shift;
402
403 foreach my $opt (keys %$confdesc) {
404 $prop->{$opt} = $confdesc->{$opt};
405 }
406
407 return $prop;
408}
409
410
411sub vmstatus {
412 my ($opt_vmid) = @_;
413
414 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
415
416 foreach my $vmid (keys %$list) {
417 next if $opt_vmid && ($vmid ne $opt_vmid);
418
419 my $d = $list->{$vmid};
420 $d->{status} = 'stopped';
421
422 my $cfspath = cfs_config_path($vmid);
423 if (my $conf = PVE::Cluster::cfs_read_file($cfspath)) {
f76a2828
DM
424 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
425 $d->{name} =~ s/[\s]//g;
e901d418
DM
426
427 $d->{cpus} = 1;
428
429 $d->{disk} = 0;
430 $d->{maxdisk} = 1;
431
432 $d->{mem} = 0;
433 $d->{maxmem} = 1024;
434
435 $d->{uptime} = 0;
436 $d->{cpu} = 0;
437
438 $d->{netout} = 0;
439 $d->{netin} = 0;
440
441 $d->{diskread} = 0;
442 $d->{diskwrite} = 0;
f76a2828
DM
443
444 }
445 }
446
447 return $list;
448}
449
7dfc49cc
DM
450
451sub print_lxc_network {
f76a2828
DM
452 my $net = shift;
453
7dfc49cc 454 die "no network link defined\n" if !$net->{link};
f76a2828 455
7dfc49cc
DM
456 my $res = "link=$net->{link}";
457
458 foreach my $k (qw(hwaddr mtu name ipv4 ipv4.gateway ipv6 ipv6.gateway)) {
f76a2828
DM
459 next if !defined($net->{$k});
460 $res .= ",$k=$net->{$k}";
461 }
7dfc49cc 462
f76a2828
DM
463 return $res;
464}
465
7dfc49cc
DM
466sub parse_lxc_network {
467 my ($data) = @_;
468
469 my $res = {};
470
471 return $res if !$data;
472
473 foreach my $pv (split (/,/, $data)) {
474 if ($pv =~ m/^(link|hwaddr|mtu|name|ipv4|ipv6|ipv4\.gateway|ipv6\.gateway)=(\S+)$/) {
475 $res->{$1} = $2;
476 } else {
477 return undef;
478 }
479 }
480
481 $res->{type} = 'veth';
482 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
483
484 return $res;
485}
f76a2828
DM
486
4871;