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