]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
Extent man description for network settings
[pve-container.git] / src / PVE / LXC.pm
CommitLineData
f76a2828
DM
1package PVE::LXC;
2
3use strict;
4use warnings;
d14a9a1b 5use POSIX qw(EINTR);
f76a2828
DM
6
7use File::Path;
8use Fcntl ':flock';
9
10use PVE::Cluster qw(cfs_register_file cfs_read_file);
c65e0a6d 11use PVE::Storage;
f76a2828
DM
12use PVE::SafeSyslog;
13use PVE::INotify;
a3249355 14use PVE::JSONSchema qw(get_standard_option);
55fa4e09 15use PVE::Tools qw($IPV6RE $IPV4RE);
f76a2828
DM
16
17use Data::Dumper;
18
19cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
20
7dfc49cc
DM
21PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
22sub verify_lxc_network {
23 my ($value, $noerr) = @_;
24
25 return $value if parse_lxc_network($value);
26
27 return undef if $noerr;
28
29 die "unable to parse network setting\n";
30}
31
f76a2828
DM
32my $nodename = PVE::INotify::nodename();
33
822de0c3
DM
34sub parse_lxc_size {
35 my ($name, $value) = @_;
36
37 if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
38 my ($res, $unit) = ($1, lc($2 || 'b'));
39
40 return $res if $unit eq 'b';
41 return $res*1024 if $unit eq 'k';
42 return $res*1024*1024 if $unit eq 'm';
43 return $res*1024*1024*1024 if $unit eq 'g';
44 }
45
46 return undef;
47}
48
f76a2828 49my $valid_lxc_keys = {
822de0c3 50 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
f76a2828
DM
51 'lxc.include' => 1,
52 'lxc.rootfs' => 1,
53 'lxc.mount' => 1,
54 'lxc.utsname' => 1,
55
54664cd3 56 'lxc.id_map' => 1,
822de0c3
DM
57
58 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
44da0641 59 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
b80dd50a
DM
60 'lxc.cgroup.cpu.cfs_period_us' => '\d+',
61 'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
62 'lxc.cgroup.cpu.shares' => '\d+',
822de0c3 63
54664cd3
DM
64 # mount related
65 'lxc.mount' => 1,
66 'lxc.mount.entry' => 1,
67 'lxc.mount.auto' => 1,
68
69 # not used by pve
b80dd50a 70 'lxc.tty' => '\d+',
54664cd3
DM
71 'lxc.pts' => 1,
72 'lxc.haltsignal' => 1,
73 'lxc.rebootsignal' => 1,
74 'lxc.stopsignal' => 1,
75 'lxc.init_cmd' => 1,
76 'lxc.console' => 1,
77 'lxc.console.logfile' => 1,
78 'lxc.devttydir' => 1,
79 'lxc.autodev' => 1,
80 'lxc.kmsg' => 1,
81 'lxc.cap.drop' => 1,
82 'lxc.cap.keep' => 1,
83 'lxc.aa_profile' => 1,
84 'lxc.aa_allow_incomplete' => 1,
85 'lxc.se_context' => 1,
86 'lxc.loglevel' => 1,
87 'lxc.logfile' => 1,
88 'lxc.environment' => 1,
0d3abdf7 89 'lxc.cgroup.devices.deny' => 1,
54664cd3
DM
90
91 # autostart
92 'lxc.start.auto' => 1,
93 'lxc.start.delay' => 1,
94 'lxc.start.order' => 1,
95 'lxc.group' => 1,
96
97 # hooks
98 'lxc.hook.pre-start' => 1,
99 'lxc.hook.pre-mount' => 1,
100 'lxc.hook.mount' => 1,
101 'lxc.hook.autodev' => 1,
102 'lxc.hook.start' => 1,
103 'lxc.hook.post-stop' => 1,
104 'lxc.hook.clone' => 1,
105
f76a2828 106 # pve related keys
117636e5
DM
107 'pve.nameserver' => sub {
108 my ($name, $value) = @_;
109 return verify_nameserver_list($value);
110 },
111 'pve.searchdomain' => sub {
112 my ($name, $value) = @_;
113 return verify_searchdomain_list($value);
114 },
a99b3509 115 'pve.onboot' => '(0|1)',
a3249355
DM
116 'pve.startup' => sub {
117 my ($name, $value) = @_;
118 return PVE::JSONSchema::pve_verify_startup_order($value);
119 },
f76a2828 120 'pve.comment' => 1,
10fc3ba5 121 'pve.disksize' => '\d+(\.\d+)?',
611fe3aa
DM
122 'pve.volid' => sub {
123 my ($name, $value) = @_;
124 PVE::Storage::parse_volume_id($value);
125 return $value;
126 },
f76a2828
DM
127};
128
93285df8 129my $valid_lxc_network_keys = {
f76a2828 130 type => 1,
f76a2828
DM
131 mtu => 1,
132 name => 1, # ifname inside container
133 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
134 hwaddr => 1,
93285df8
DM
135};
136
137my $valid_pve_network_keys = {
8a04b6c7 138 bridge => 1,
2b1fc2ea
DM
139 tag => 1,
140 firewall => 1,
93285df8
DM
141 ip => 1,
142 gw => 1,
143 ip6 => 1,
144 gw6 => 1,
f76a2828
DM
145};
146
147my $lxc_array_configs = {
148 'lxc.network' => 1,
149 'lxc.mount' => 1,
150 'lxc.include' => 1,
5b4657d0 151 'lxc.id_map' => 1,
0d3abdf7 152 'lxc.cgroup.devices.deny' => 1,
f76a2828
DM
153};
154
155sub write_lxc_config {
156 my ($filename, $data) = @_;
157
158 my $raw = "";
159
160 return $raw if !$data;
161
162 my $done_hash = { digest => 1};
7dfc49cc 163
63e9c5ca
DM
164 my $dump_entry = sub {
165 my ($k) = @_;
166 my $value = $data->{$k};
167 return if !defined($value);
168 return if $done_hash->{$k};
f76a2828 169 $done_hash->{$k} = 1;
63e9c5ca
DM
170 if (ref($value)) {
171 die "got unexpected reference for '$k'"
172 if !$lxc_array_configs->{$k};
173 foreach my $v (@$value) {
5b4657d0
DM
174 $raw .= "$k = $v\n";
175 }
176 } else {
63e9c5ca 177 $raw .= "$k = $value\n";
5b4657d0 178 }
63e9c5ca
DM
179 };
180
181 # Note: Order is important! Include defaults first, so that we
182 # can overwrite them later.
183 &$dump_entry('lxc.include');
184
185 foreach my $k (sort keys %$data) {
186 next if $k !~ m/^lxc\./;
187 &$dump_entry($k);
f76a2828 188 }
7dfc49cc 189
f76a2828 190 foreach my $k (sort keys %$data) {
fff3a342 191 next if $k !~ m/^pve\./;
63e9c5ca 192 &$dump_entry($k);
fff3a342
DM
193 }
194
160f0941 195 my $network_count = 0;
fff3a342 196 foreach my $k (sort keys %$data) {
f76a2828
DM
197 next if $k !~ m/^net\d+$/;
198 $done_hash->{$k} = 1;
199 my $net = $data->{$k};
160f0941 200 $network_count++;
f76a2828
DM
201 $raw .= "lxc.network.type = $net->{type}\n";
202 foreach my $subkey (sort keys %$net) {
203 next if $subkey eq 'type';
93285df8
DM
204 if ($valid_lxc_network_keys->{$subkey}) {
205 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
206 } elsif ($valid_pve_network_keys->{$subkey}) {
207 $raw .= "pve.network.$subkey = $net->{$subkey}\n";
208 } else {
209 die "found invalid network key '$subkey'";
210 }
f76a2828
DM
211 }
212 }
213
160f0941
DM
214 if (!$network_count) {
215 $raw .= "lxc.network.type = empty\n";
216 }
217
f76a2828
DM
218 foreach my $k (sort keys %$data) {
219 next if $done_hash->{$k};
220 die "found un-written value in config - implement this!";
221 }
222
f76a2828
DM
223 return $raw;
224}
225
822de0c3
DM
226sub parse_lxc_option {
227 my ($name, $value) = @_;
228
229 my $parser = $valid_lxc_keys->{$name};
230
fe81a350 231 die "invalid key '$name'\n" if !defined($parser);
822de0c3
DM
232
233 if ($parser eq '1') {
234 return $value;
235 } elsif (ref($parser)) {
236 my $res = &$parser($name, $value);
237 return $res if defined($res);
238 } else {
239 # assume regex
240 return $value if $value =~ m/^$parser$/;
241 }
242
243 die "unable to parse value '$value' for option '$name'\n";
244}
245
f76a2828
DM
246sub parse_lxc_config {
247 my ($filename, $raw) = @_;
248
249 return undef if !defined($raw);
250
251 my $data = {
252 digest => Digest::SHA::sha1_hex($raw),
253 };
254
255 $filename =~ m|/lxc/(\d+)/config$|
256 || die "got strange filename '$filename'";
257
258 my $vmid = $1;
259
260 my $network_counter = 0;
261 my $network_list = [];
262 my $host_ifnames = {};
263
264 my $find_next_hostif_name = sub {
265 for (my $i = 0; $i < 10; $i++) {
266 my $name = "veth${vmid}.$i";
267 if (!$host_ifnames->{$name}) {
268 $host_ifnames->{$name} = 1;
269 return $name;
270 }
271 }
272
273 die "unable to find free host_ifname"; # should not happen
274 };
7dfc49cc 275
f76a2828
DM
276 my $push_network = sub {
277 my ($netconf) = @_;
278 return if !$netconf;
279 push @{$network_list}, $netconf;
280 $network_counter++;
281 if (my $netname = $netconf->{'veth.pair'}) {
282 if ($netname =~ m/^veth(\d+).(\d)$/) {
283 die "wrong vmid for network interface pair\n" if $1 != $vmid;
284 my $host_ifnames->{$netname} = 1;
285 } else {
286 die "wrong network interface pair\n";
287 }
288 }
289 };
290
291 my $network;
7dfc49cc 292
f76a2828
DM
293 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
294 my $line = $1;
295
296 next if $line =~ m/^\#/;
297 next if $line =~ m/^\s*$/;
298
299 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
300 my ($subkey, $value) = ($1, $2);
301 if ($subkey eq 'type') {
302 &$push_network($network);
303 $network = { type => $value };
93285df8
DM
304 } elsif ($valid_lxc_network_keys->{$subkey}) {
305 $network->{$subkey} = $value;
306 } else {
307 die "unable to parse config line: $line\n";
308 }
309 next;
310 }
311 if ($line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
312 my ($subkey, $value) = ($1, $2);
313 if ($valid_pve_network_keys->{$subkey}) {
f76a2828
DM
314 $network->{$subkey} = $value;
315 } else {
316 die "unable to parse config line: $line\n";
317 }
f76a2828
DM
318 next;
319 }
320 if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
321 my ($name, $value) = ($1, $2);
322 $data->{$name} = $value;
323 next;
324 }
54664cd3 325 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
f76a2828
DM
326 my ($name, $value) = ($1, $2);
327
5b4657d0
DM
328 if ($lxc_array_configs->{$name}) {
329 $data->{$name} = [] if !defined($data->{$name});
330 push @{$data->{$name}}, parse_lxc_option($name, $value);
331 } else {
332 die "multiple definitions for $name\n" if defined($data->{$name});
333 $data->{$name} = parse_lxc_option($name, $value);
334 }
335
f76a2828
DM
336 next;
337 }
338
339 die "unable to parse config line: $line\n";
340 }
341
342 &$push_network($network);
343
344 foreach my $net (@{$network_list}) {
160f0941 345 next if $net->{type} eq 'empty'; # skip
f76a2828
DM
346 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
347 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
348 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
349
350 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
351 $data->{"net$1"} = $net;
352 }
7dfc49cc
DM
353 }
354
355 return $data;
f76a2828
DM
356}
357
358sub config_list {
359 my $vmlist = PVE::Cluster::get_vmlist();
360 my $res = {};
361 return $res if !$vmlist || !$vmlist->{ids};
362 my $ids = $vmlist->{ids};
363
364 foreach my $vmid (keys %$ids) {
365 next if !$vmid; # skip CT0
366 my $d = $ids->{$vmid};
367 next if !$d->{node} || $d->{node} ne $nodename;
368 next if !$d->{type} || $d->{type} ne 'lxc';
369 $res->{$vmid}->{type} = 'lxc';
370 }
371 return $res;
372}
373
374sub cfs_config_path {
375 my ($vmid, $node) = @_;
376
377 $node = $nodename if !$node;
378 return "nodes/$node/lxc/$vmid/config";
379}
380
9c2d4ce9
DM
381sub config_file {
382 my ($vmid, $node) = @_;
383
384 my $cfspath = cfs_config_path($vmid, $node);
385 return "/etc/pve/$cfspath";
386}
387
f76a2828
DM
388sub load_config {
389 my ($vmid) = @_;
390
391 my $cfspath = cfs_config_path($vmid);
392
393 my $conf = PVE::Cluster::cfs_read_file($cfspath);
394 die "container $vmid does not exists\n" if !defined($conf);
395
396 return $conf;
397}
398
5b4657d0
DM
399sub create_config {
400 my ($vmid, $conf) = @_;
401
402 my $dir = "/etc/pve/nodes/$nodename/lxc";
403 mkdir $dir;
404
405 $dir .= "/$vmid";
406 mkdir($dir) || die "unable to create container configuration directory - $!\n";
407
408 write_config($vmid, $conf);
409}
410
411sub destroy_config {
412 my ($vmid) = @_;
413
414 my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
415 File::Path::rmtree($dir);
416}
417
f76a2828
DM
418sub write_config {
419 my ($vmid, $conf) = @_;
420
421 my $cfspath = cfs_config_path($vmid);
422
423 PVE::Cluster::cfs_write_file($cfspath, $conf);
424}
425
9c2d4ce9
DM
426my $tempcounter = 0;
427sub write_temp_config {
428 my ($vmid, $conf) = @_;
7dfc49cc 429
9c2d4ce9
DM
430 $tempcounter++;
431 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
432
433 my $raw = write_lxc_config($filename, $conf);
434
435 PVE::Tools::file_set_contents($filename, $raw);
7dfc49cc 436
9c2d4ce9
DM
437 return $filename;
438}
439
d14a9a1b
DM
440# flock: we use one file handle per process, so lock file
441# can be called multiple times and succeeds for the same process.
442
443my $lock_handles = {};
444my $lockdir = "/run/lock/lxc";
445
446sub lock_filename {
447 my ($vmid) = @_;
448
449 return "$lockdir/pve-config-{$vmid}.lock";
450}
451
452sub lock_aquire {
453 my ($vmid, $timeout) = @_;
454
455 $timeout = 10 if !$timeout;
456 my $mode = LOCK_EX;
457
458 my $filename = lock_filename($vmid);
459
f99e8278
AD
460 mkdir $lockdir if !-d $lockdir;
461
d14a9a1b
DM
462 my $lock_func = sub {
463 if (!$lock_handles->{$$}->{$filename}) {
464 my $fh = new IO::File(">>$filename") ||
465 die "can't open file - $!\n";
466 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
467 }
468
469 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
470 print STDERR "trying to aquire lock...";
471 my $success;
472 while(1) {
473 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
474 # try again on EINTR (see bug #273)
475 if ($success || ($! != EINTR)) {
476 last;
477 }
478 }
479 if (!$success) {
480 print STDERR " failed\n";
481 die "can't aquire lock - $!\n";
482 }
483
484 $lock_handles->{$$}->{$filename}->{refcount}++;
485
486 print STDERR " OK\n";
487 }
488 };
489
490 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
491 my $err = $@;
492 if ($err) {
493 die "can't lock file '$filename' - $err";
494 }
495}
496
497sub lock_release {
498 my ($vmid) = @_;
499
500 my $filename = lock_filename($vmid);
501
502 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
503 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
504 if ($refcount <= 0) {
505 $lock_handles->{$$}->{$filename} = undef;
506 close ($fh);
507 }
508 }
509}
510
f76a2828
DM
511sub lock_container {
512 my ($vmid, $timeout, $code, @param) = @_;
513
d14a9a1b 514 my $res;
f76a2828 515
d14a9a1b
DM
516 lock_aquire($vmid, $timeout);
517 eval { $res = &$code(@param) };
518 my $err = $@;
519 lock_release($vmid);
f76a2828 520
d14a9a1b 521 die $err if $err;
f76a2828
DM
522
523 return $res;
524}
525
526my $confdesc = {
527 onboot => {
528 optional => 1,
529 type => 'boolean',
530 description => "Specifies whether a VM will be started during system bootup.",
531 default => 0,
532 },
a3249355 533 startup => get_standard_option('pve-startup-order'),
45573f7c 534 cpulimit => {
f76a2828 535 optional => 1,
8daf6a1c 536 type => 'number',
45573f7c 537 description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
44da0641
DM
538 minimum => 0,
539 maximum => 128,
540 default => 0,
f76a2828
DM
541 },
542 cpuunits => {
543 optional => 1,
544 type => 'integer',
545 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.",
546 minimum => 0,
547 maximum => 500000,
548 default => 1000,
549 },
550 memory => {
551 optional => 1,
552 type => 'integer',
553 description => "Amount of RAM for the VM in MB.",
554 minimum => 16,
555 default => 512,
556 },
557 swap => {
558 optional => 1,
559 type => 'integer',
560 description => "Amount of SWAP for the VM in MB.",
561 minimum => 0,
562 default => 512,
563 },
564 disk => {
565 optional => 1,
566 type => 'number',
567 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
568 minimum => 0,
10fc3ba5 569 default => 4,
f76a2828
DM
570 },
571 hostname => {
572 optional => 1,
573 description => "Set a host name for the container.",
574 type => 'string',
575 maxLength => 255,
576 },
577 description => {
578 optional => 1,
579 type => 'string',
580 description => "Container description. Only used on the configuration web interface.",
581 },
582 searchdomain => {
583 optional => 1,
584 type => 'string',
585 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
586 },
587 nameserver => {
588 optional => 1,
589 type => 'string',
590 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.",
591 },
ec52ac21
DM
592};
593
594my $MAX_LXC_NETWORKS = 10;
595for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
596 $confdesc->{"net$i"} = {
f76a2828
DM
597 optional => 1,
598 type => 'string', format => 'pve-lxc-network',
b156e7cd 599 description => "Specifies network interfaces for the container.\n\n".
600 "The string should have the follow format:\n\n".
601 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
602 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
603 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
604 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
7dfc49cc 605 };
ec52ac21
DM
606}
607
608sub option_exists {
609 my ($name) = @_;
610
611 return defined($confdesc->{$name});
612}
f76a2828
DM
613
614# add JSON properties for create and set function
615sub json_config_properties {
616 my $prop = shift;
617
618 foreach my $opt (keys %$confdesc) {
619 $prop->{$opt} = $confdesc->{$opt};
620 }
621
622 return $prop;
623}
624
822de0c3
DM
625# container status helpers
626
627sub list_active_containers {
628
629 my $filename = "/proc/net/unix";
630
631 # similar test is used by lcxcontainers.c: list_active_containers
632 my $res = {};
633
634 my $fh = IO::File->new ($filename, "r");
635 return $res if !$fh;
636
637 while (defined(my $line = <$fh>)) {
638 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
639 my $path = $1;
640 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
641 $res->{$1} = 1;
642 }
643 }
644 }
645
646 close($fh);
647
648 return $res;
649}
f76a2828 650
5c752bbf
DM
651# warning: this is slow
652sub check_running {
653 my ($vmid) = @_;
654
655 my $active_hash = list_active_containers();
656
657 return 1 if defined($active_hash->{$vmid});
658
659 return undef;
660}
661
10fc3ba5
DM
662sub get_container_disk_usage {
663 my ($vmid) = @_;
664
665 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
666
667 my $res = {
668 total => 0,
669 used => 0,
670 avail => 0,
671 };
672
673 my $parser = sub {
674 my $line = shift;
675 if (my ($fsid, $total, $used, $avail) = $line =~
676 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
677 $res = {
678 total => $total,
679 used => $used,
680 avail => $avail,
681 };
682 }
683 };
684 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
685 warn $@ if $@;
686
687 return $res;
688}
689
f76a2828
DM
690sub vmstatus {
691 my ($opt_vmid) = @_;
692
693 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
694
822de0c3
DM
695 my $active_hash = list_active_containers();
696
f76a2828 697 foreach my $vmid (keys %$list) {
f76a2828 698 my $d = $list->{$vmid};
10fc3ba5
DM
699
700 my $running = defined($active_hash->{$vmid});
701
702 $d->{status} = $running ? 'running' : 'stopped';
f76a2828
DM
703
704 my $cfspath = cfs_config_path($vmid);
238a56cb
DM
705 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
706
707 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
708 $d->{name} =~ s/[\s]//g;
e901d418 709
44da0641
DM
710 $d->{cpus} = 0;
711
712 my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
713 my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
714
715 if ($cfs_period_us && $cfs_quota_us) {
716 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
717 }
238a56cb
DM
718
719 $d->{disk} = 0;
10fc3ba5
DM
720 $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
721 int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
722
238a56cb 723 if (my $private = $conf->{'lxc.rootfs'}) {
10fc3ba5
DM
724 if ($private =~ m!^/!) {
725 my $res = PVE::Tools::df($private, 2);
726 $d->{disk} = $res->{used};
727 $d->{maxdisk} = $res->{total};
728 } elsif ($running) {
6ed8c6dd 729 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
10fc3ba5
DM
730 my $res = get_container_disk_usage($vmid);
731 $d->{disk} = $res->{used};
732 $d->{maxdisk} = $res->{total};
733 }
734 }
238a56cb
DM
735 }
736
737 $d->{mem} = 0;
738 $d->{swap} = 0;
739 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
44da0641 740 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
e901d418 741
238a56cb
DM
742 $d->{uptime} = 0;
743 $d->{cpu} = 0;
e901d418 744
238a56cb
DM
745 $d->{netout} = 0;
746 $d->{netin} = 0;
f76a2828 747
238a56cb
DM
748 $d->{diskread} = 0;
749 $d->{diskwrite} = 0;
f76a2828 750 }
238a56cb
DM
751
752 foreach my $vmid (keys %$list) {
753 my $d = $list->{$vmid};
754 next if $d->{status} ne 'running';
f76a2828 755
22a77285
DM
756 $d->{uptime} = 100; # fixme:
757
238a56cb
DM
758 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
759 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
b5289322
AD
760
761 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
762 my @bytes = split /\n/, $blkio_bytes;
763 foreach my $byte (@bytes) {
764 my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/;
765 $d->{diskread} = $2 if $key eq 'Read';
766 $d->{diskwrite} = $2 if $key eq 'Write';
767 }
238a56cb
DM
768 }
769
f76a2828
DM
770 return $list;
771}
772
7dfc49cc
DM
773
774sub print_lxc_network {
f76a2828
DM
775 my $net = shift;
776
8a04b6c7 777 die "no network bridge defined\n" if !$net->{bridge};
f76a2828 778
8a04b6c7 779 my $res = "bridge=$net->{bridge}";
7dfc49cc 780
2b1fc2ea 781 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
782 next if !defined($net->{$k});
783 $res .= ",$k=$net->{$k}";
784 }
7dfc49cc 785
f76a2828
DM
786 return $res;
787}
788
7dfc49cc
DM
789sub parse_lxc_network {
790 my ($data) = @_;
791
792 my $res = {};
793
794 return $res if !$data;
795
796 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 797 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
798 $res->{$1} = $2;
799 } else {
800 return undef;
801 }
802 }
803
804 $res->{type} = 'veth';
805 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
806
807 return $res;
808}
f76a2828 809
238a56cb
DM
810sub read_cgroup_value {
811 my ($group, $vmid, $name, $full) = @_;
812
813 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
814
815 return PVE::Tools::file_get_contents($path) if $full;
816
817 return PVE::Tools::file_read_firstline($path);
818}
819
52f1d76b
DM
820sub find_lxc_console_pids {
821
822 my $res = {};
823
824 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
825 my ($pid) = @_;
826
827 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
828 return if !$cmdline;
829
830 my @args = split(/\0/, $cmdline);
831
832 # serach for lxc-console -n <vmid>
833 return if scalar(@args) != 3;
834 return if $args[1] ne '-n';
835 return if $args[2] !~ m/^\d+$/;
836 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
837
838 my $vmid = $args[2];
839
840 push @{$res->{$vmid}}, $pid;
841 });
842
843 return $res;
844}
845
55fa4e09
DM
846my $ipv4_reverse_mask = [
847 '0.0.0.0',
848 '128.0.0.0',
849 '192.0.0.0',
850 '224.0.0.0',
851 '240.0.0.0',
852 '248.0.0.0',
853 '252.0.0.0',
854 '254.0.0.0',
855 '255.0.0.0',
856 '255.128.0.0',
857 '255.192.0.0',
858 '255.224.0.0',
859 '255.240.0.0',
860 '255.248.0.0',
861 '255.252.0.0',
862 '255.254.0.0',
863 '255.255.0.0',
864 '255.255.128.0',
865 '255.255.192.0',
866 '255.255.224.0',
867 '255.255.240.0',
868 '255.255.248.0',
869 '255.255.252.0',
870 '255.255.254.0',
871 '255.255.255.0',
872 '255.255.255.128',
873 '255.255.255.192',
874 '255.255.255.224',
875 '255.255.255.240',
876 '255.255.255.248',
877 '255.255.255.252',
878 '255.255.255.254',
879 '255.255.255.255',
880];
881
882# Note: we cannot use Net:IP, because that only allows strict
883# CIDR networks
884sub parse_ipv4_cidr {
885 my ($cidr, $noerr) = @_;
886
887 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
888 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
889 }
890
891 return undef if $noerr;
892
893 die "unable to parse ipv4 address/mask\n";
894}
93285df8 895
b80dd50a
DM
896sub lxc_conf_to_pve {
897 my ($vmid, $lxc_conf) = @_;
898
899 my $properties = json_config_properties();
900
901 my $conf = { digest => $lxc_conf->{digest} };
902
903 foreach my $k (keys %$properties) {
904
905 if ($k eq 'description') {
906 if (my $raw = $lxc_conf->{'pve.comment'}) {
907 $conf->{$k} = PVE::Tools::decode_text($raw);
908 }
a99b3509
DM
909 } elsif ($k eq 'onboot') {
910 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
a3249355
DM
911 } elsif ($k eq 'startup') {
912 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
b80dd50a
DM
913 } elsif ($k eq 'hostname') {
914 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
ffa1d001
DM
915 } elsif ($k eq 'nameserver') {
916 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
917 } elsif ($k eq 'searchdomain') {
918 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
b80dd50a
DM
919 } elsif ($k eq 'memory') {
920 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
921 $conf->{$k} = int($value / (1024*1024));
922 }
923 } elsif ($k eq 'swap') {
924 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
925 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
926 $conf->{$k} = int(($value -$mem) / (1024*1024));
927 }
45573f7c 928 } elsif ($k eq 'cpulimit') {
b80dd50a
DM
929 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
930 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
931
932 if ($cfs_period_us && $cfs_quota_us) {
8daf6a1c 933 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
b80dd50a
DM
934 } else {
935 $conf->{$k} = 0;
936 }
937 } elsif ($k eq 'cpuunits') {
938 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
10fc3ba5
DM
939 } elsif ($k eq 'disk') {
940 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
941 $lxc_conf->{'pve.disksize'} : 0;
b80dd50a
DM
942 } elsif ($k =~ m/^net\d$/) {
943 my $net = $lxc_conf->{$k};
944 next if !$net;
945 $conf->{$k} = print_lxc_network($net);
946 }
947 }
948
949 return $conf;
950}
951
117636e5
DM
952# verify and cleanup nameserver list (replace \0 with ' ')
953sub verify_nameserver_list {
954 my ($nameserver_list) = @_;
955
956 my @list = ();
957 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
958 PVE::JSONSchema::pve_verify_ip($server);
959 push @list, $server;
960 }
961
962 return join(' ', @list);
963}
964
965sub verify_searchdomain_list {
966 my ($searchdomain_list) = @_;
967
968 my @list = ();
969 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
970 # todo: should we add checks for valid dns domains?
971 push @list, $server;
972 }
973
974 return join(' ', @list);
975}
976
93285df8
DM
977sub update_lxc_config {
978 my ($vmid, $conf, $running, $param, $delete) = @_;
979
980 # fixme: hotplug
981 die "unable to modify config while container is running\n" if $running;
3a43e41a 982
93285df8
DM
983 if (defined($delete)) {
984 foreach my $opt (@$delete) {
985 if ($opt eq 'hostname' || $opt eq 'memory') {
986 die "unable to delete required option '$opt'\n";
987 } elsif ($opt eq 'swap') {
44da0641 988 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
3a43e41a 989 cgroups_write("memory", $vmid, "memsw.limit_in_bytes", -1, $running);
93285df8
DM
990 } elsif ($opt eq 'description') {
991 delete $conf->{'pve.comment'};
a99b3509
DM
992 } elsif ($opt eq 'onboot') {
993 delete $conf->{'pve.onboot'};
a3249355
DM
994 } elsif ($opt eq 'startup') {
995 delete $conf->{'pve.startup'};
ffa1d001
DM
996 } elsif ($opt eq 'nameserver') {
997 delete $conf->{'pve.nameserver'};
998 } elsif ($opt eq 'searchdomain') {
999 delete $conf->{'pve.searchdomain'};
93285df8
DM
1000 } elsif ($opt =~ m/^net\d$/) {
1001 delete $conf->{$opt};
1002 } else {
1003 die "implement me"
1004 }
1005 }
1006 }
1007
1008 foreach my $opt (keys %$param) {
1009 my $value = $param->{$opt};
1010 if ($opt eq 'hostname') {
1011 $conf->{'lxc.utsname'} = $value;
a99b3509
DM
1012 } elsif ($opt eq 'onboot') {
1013 $conf->{'pve.onboot'} = $value ? 1 : 0;
a3249355
DM
1014 } elsif ($opt eq 'startup') {
1015 $conf->{'pve.startup'} = $value;
ffa1d001 1016 } elsif ($opt eq 'nameserver') {
117636e5 1017 my $list = verify_nameserver_list($value);
c325b32f 1018 $conf->{'pve.nameserver'} = $list;
ffa1d001 1019 } elsif ($opt eq 'searchdomain') {
117636e5 1020 my $list = verify_searchdomain_list($value);
c325b32f 1021 $conf->{'pve.searchdomain'} = $list;
93285df8
DM
1022 } elsif ($opt eq 'memory') {
1023 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
3a43e41a 1024 cgroups_write("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024, $running);
93285df8 1025 } elsif ($opt eq 'swap') {
44da0641
DM
1026 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
1027 $mem = $param->{memory}*1024*1024 if $param->{memory};
1028 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
3a43e41a
AD
1029 cgroups_write("memory", $vmid, "memsw.limit_in_bytes", $mem + $value*1024*1024, $running);
1030
45573f7c 1031 } elsif ($opt eq 'cpulimit') {
44da0641
DM
1032 if ($value > 0) {
1033 my $cfs_period_us = 100000;
1034 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
1035 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
3a43e41a 1036 cgroups_write("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value, $running);
44da0641
DM
1037 } else {
1038 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1039 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
3a43e41a 1040 cgroups_write("cpu", $vmid, "cpu.cfs_quota_us", -1, $running);
44da0641 1041 }
b80dd50a
DM
1042 } elsif ($opt eq 'cpuunits') {
1043 $conf->{'lxc.cgroup.cpu.shares'} = $value;
3a43e41a 1044 cgroups_write("cpu", $vmid, "cpu.shares", $value, $running);
93285df8
DM
1045 } elsif ($opt eq 'description') {
1046 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
10fc3ba5
DM
1047 } elsif ($opt eq 'disk') {
1048 $conf->{'pve.disksize'} = $value;
93285df8
DM
1049 } elsif ($opt =~ m/^net(\d+)$/) {
1050 my $netid = $1;
1051 my $net = PVE::LXC::parse_lxc_network($value);
1052 $net->{'veth.pair'} = "veth${vmid}.$netid";
1053 $conf->{$opt} = $net;
1054 } else {
1055 die "implement me"
1056 }
1057 }
1058}
c325b32f
DM
1059
1060sub get_primary_ips {
1061 my ($conf) = @_;
1062
1063 # return data from net0
1064
1065 my $net = $conf->{net0};
1066 return undef if !$net;
1067
1068 my $ipv4 = $net->{ip};
1069 $ipv4 =~ s!/\d+$!! if $ipv4;
1070 my $ipv6 = $net->{ip};
1071 $ipv6 =~ s!/\d+$!! if $ipv6;
1072
1073 return ($ipv4, $ipv6);
1074}
148d1cb4
DM
1075
1076sub destory_lxc_container {
1077 my ($storage_cfg, $vmid, $conf) = @_;
1078
1079 if (my $volid = $conf->{'pve.volid'}) {
1080
1081 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
1082 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1083
1084 PVE::Storage::vdisk_free($storage_cfg, $volid);
1085
1086 destroy_config($vmid);
1087
1088 } else {
1089 my $cmd = ['lxc-destroy', '-n', $vmid ];
1090
1091 PVE::Tools::run_command($cmd);
1092 }
1093}
3a43e41a
AD
1094
1095sub cgroups_write {
1096 my ($controller, $vmid, $option, $value, $running) = @_;
1097
1098 return if !$running;
1099 my $path = "/sys/fs/cgroup/$controller/lxc/$vmid/$option";
1100 PVE::ProcFSTools::write_proc_entry($path, $value);
1101
1102}
238a56cb 1103
f76a2828 11041;