]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
avoid warning about undefined values
[pve-container.git] / src / PVE / LXC.pm
1 package PVE::LXC;
2
3 use strict;
4 use warnings;
5 use POSIX qw(EINTR);
6
7 use File::Path;
8 use Fcntl ':flock';
9
10 use PVE::Cluster qw(cfs_register_file cfs_read_file);
11 use PVE::Storage;
12 use PVE::SafeSyslog;
13 use PVE::INotify;
14 use PVE::JSONSchema qw(get_standard_option);
15 use PVE::Tools qw($IPV6RE $IPV4RE);
16 use PVE::Network;
17
18 use Data::Dumper;
19
20 cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
21
22 PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
23 sub 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
33 my $nodename = PVE::INotify::nodename();
34
35 sub 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
50 my $valid_lxc_keys = {
51 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
52 'lxc.include' => 1,
53 'lxc.rootfs' => 1,
54 'lxc.mount' => 1,
55 'lxc.utsname' => 1,
56
57 'lxc.id_map' => 1,
58
59 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
60 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
61 'lxc.cgroup.cpu.cfs_period_us' => '\d+',
62 'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
63 'lxc.cgroup.cpu.shares' => '\d+',
64
65 # mount related
66 'lxc.mount' => 1,
67 'lxc.mount.entry' => 1,
68 'lxc.mount.auto' => 1,
69
70 # not used by pve
71 'lxc.tty' => '\d+',
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,
90 'lxc.cgroup.devices.deny' => 1,
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
107 # pve related keys
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 },
116 'pve.onboot' => '(0|1)',
117 'pve.startup' => sub {
118 my ($name, $value) = @_;
119 return PVE::JSONSchema::pve_verify_startup_order($value);
120 },
121 'pve.comment' => 1,
122 'pve.disksize' => '\d+(\.\d+)?',
123 'pve.volid' => sub {
124 my ($name, $value) = @_;
125 PVE::Storage::parse_volume_id($value);
126 return $value;
127 },
128 };
129
130 my $valid_lxc_network_keys = {
131 type => 1,
132 mtu => 1,
133 name => 1, # ifname inside container
134 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
135 hwaddr => 1,
136 };
137
138 my $valid_pve_network_keys = {
139 bridge => 1,
140 tag => 1,
141 firewall => 1,
142 ip => 1,
143 gw => 1,
144 ip6 => 1,
145 gw6 => 1,
146 };
147
148 my $lxc_array_configs = {
149 'lxc.network' => 1,
150 'lxc.mount' => 1,
151 'lxc.include' => 1,
152 'lxc.id_map' => 1,
153 'lxc.cgroup.devices.deny' => 1,
154 };
155
156 sub write_lxc_config {
157 my ($filename, $data) = @_;
158
159 my $raw = "";
160
161 return $raw if !$data;
162
163 my $done_hash = { digest => 1};
164
165 my $dump_entry = sub {
166 my ($k) = @_;
167 my $value = $data->{$k};
168 return if !defined($value);
169 return if $done_hash->{$k};
170 $done_hash->{$k} = 1;
171 if (ref($value)) {
172 die "got unexpected reference for '$k'"
173 if !$lxc_array_configs->{$k};
174 foreach my $v (@$value) {
175 $raw .= "$k = $v\n";
176 }
177 } else {
178 $raw .= "$k = $value\n";
179 }
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);
189 }
190
191 foreach my $k (sort keys %$data) {
192 next if $k !~ m/^pve\./;
193 &$dump_entry($k);
194 }
195
196 my $network_count = 0;
197 foreach my $k (sort keys %$data) {
198 next if $k !~ m/^net\d+$/;
199 $done_hash->{$k} = 1;
200 my $net = $data->{$k};
201 $network_count++;
202 $raw .= "lxc.network.type = $net->{type}\n";
203 foreach my $subkey (sort keys %$net) {
204 next if $subkey eq 'type';
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 }
212 }
213 }
214
215 if (!$network_count) {
216 $raw .= "lxc.network.type = empty\n";
217 }
218
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
224 return $raw;
225 }
226
227 sub parse_lxc_option {
228 my ($name, $value) = @_;
229
230 my $parser = $valid_lxc_keys->{$name};
231
232 die "invalid key '$name'\n" if !defined($parser);
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
247 sub 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 };
276
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;
293
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 };
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}) {
315 $network->{$subkey} = $value;
316 } else {
317 die "unable to parse config line: $line\n";
318 }
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 }
326 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
327 my ($name, $value) = ($1, $2);
328
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
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}) {
346 next if $net->{type} eq 'empty'; # skip
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 }
354 }
355
356 return $data;
357 }
358
359 sub 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
375 sub cfs_config_path {
376 my ($vmid, $node) = @_;
377
378 $node = $nodename if !$node;
379 return "nodes/$node/lxc/$vmid/config";
380 }
381
382 sub config_file {
383 my ($vmid, $node) = @_;
384
385 my $cfspath = cfs_config_path($vmid, $node);
386 return "/etc/pve/$cfspath";
387 }
388
389 sub 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
400 sub 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
412 sub destroy_config {
413 my ($vmid) = @_;
414
415 my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
416 File::Path::rmtree($dir);
417 }
418
419 sub 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
427 my $tempcounter = 0;
428 sub write_temp_config {
429 my ($vmid, $conf) = @_;
430
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);
437
438 return $filename;
439 }
440
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
444 my $lock_handles = {};
445 my $lockdir = "/run/lock/lxc";
446
447 sub lock_filename {
448 my ($vmid) = @_;
449
450 return "$lockdir/pve-config-{$vmid}.lock";
451 }
452
453 sub 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
461 mkdir $lockdir if !-d $lockdir;
462
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
498 sub 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
512 sub lock_container {
513 my ($vmid, $timeout, $code, @param) = @_;
514
515 my $res;
516
517 lock_aquire($vmid, $timeout);
518 eval { $res = &$code(@param) };
519 my $err = $@;
520 lock_release($vmid);
521
522 die $err if $err;
523
524 return $res;
525 }
526
527 my $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 },
534 startup => get_standard_option('pve-startup-order'),
535 cpulimit => {
536 optional => 1,
537 type => 'number',
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.",
539 minimum => 0,
540 maximum => 128,
541 default => 0,
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,
570 default => 4,
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 },
593 };
594
595 my $MAX_LXC_NETWORKS = 10;
596 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
597 $confdesc->{"net$i"} = {
598 optional => 1,
599 type => 'string', format => 'pve-lxc-network',
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>]",
606 };
607 }
608
609 sub option_exists {
610 my ($name) = @_;
611
612 return defined($confdesc->{$name});
613 }
614
615 # add JSON properties for create and set function
616 sub 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
626 # container status helpers
627
628 sub 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 }
651
652 # warning: this is slow
653 sub 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
663 sub 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
691 sub vmstatus {
692 my ($opt_vmid) = @_;
693
694 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
695
696 my $active_hash = list_active_containers();
697
698 foreach my $vmid (keys %$list) {
699 my $d = $list->{$vmid};
700
701 my $running = defined($active_hash->{$vmid});
702
703 $d->{status} = $running ? 'running' : 'stopped';
704
705 my $cfspath = cfs_config_path($vmid);
706 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
707
708 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
709 $d->{name} =~ s/[\s]//g;
710
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 }
719
720 $d->{disk} = 0;
721 $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
722 int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
723
724 if (my $private = $conf->{'lxc.rootfs'}) {
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) {
730 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
731 my $res = get_container_disk_usage($vmid);
732 $d->{disk} = $res->{used};
733 $d->{maxdisk} = $res->{total};
734 }
735 }
736 }
737
738 $d->{mem} = 0;
739 $d->{swap} = 0;
740 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
741 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
742
743 $d->{uptime} = 0;
744 $d->{cpu} = 0;
745
746 $d->{netout} = 0;
747 $d->{netin} = 0;
748
749 $d->{diskread} = 0;
750 $d->{diskwrite} = 0;
751 }
752
753 foreach my $vmid (keys %$list) {
754 my $d = $list->{$vmid};
755 next if $d->{status} ne 'running';
756
757 $d->{uptime} = 100; # fixme:
758
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};
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 if (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 }
769 }
770 }
771
772 return $list;
773 }
774
775
776 sub print_lxc_network {
777 my $net = shift;
778
779 die "no network bridge defined\n" if !$net->{bridge};
780
781 my $res = "bridge=$net->{bridge}";
782
783 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
784 next if !defined($net->{$k});
785 $res .= ",$k=$net->{$k}";
786 }
787
788 return $res;
789 }
790
791 sub parse_lxc_network {
792 my ($data) = @_;
793
794 my $res = {};
795
796 return $res if !$data;
797
798 foreach my $pv (split (/,/, $data)) {
799 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
800 $res->{$1} = $2;
801 } else {
802 return undef;
803 }
804 }
805
806 $res->{type} = 'veth';
807 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
808
809 return $res;
810 }
811
812 sub read_cgroup_value {
813 my ($group, $vmid, $name, $full) = @_;
814
815 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
816
817 return PVE::Tools::file_get_contents($path) if $full;
818
819 return PVE::Tools::file_read_firstline($path);
820 }
821
822 sub write_cgroup_value {
823 my ($group, $vmid, $name, $value) = @_;
824
825 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
826 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
827
828 }
829
830 sub find_lxc_console_pids {
831
832 my $res = {};
833
834 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
835 my ($pid) = @_;
836
837 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
838 return if !$cmdline;
839
840 my @args = split(/\0/, $cmdline);
841
842 # serach for lxc-console -n <vmid>
843 return if scalar(@args) != 3;
844 return if $args[1] ne '-n';
845 return if $args[2] !~ m/^\d+$/;
846 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
847
848 my $vmid = $args[2];
849
850 push @{$res->{$vmid}}, $pid;
851 });
852
853 return $res;
854 }
855
856 my $ipv4_reverse_mask = [
857 '0.0.0.0',
858 '128.0.0.0',
859 '192.0.0.0',
860 '224.0.0.0',
861 '240.0.0.0',
862 '248.0.0.0',
863 '252.0.0.0',
864 '254.0.0.0',
865 '255.0.0.0',
866 '255.128.0.0',
867 '255.192.0.0',
868 '255.224.0.0',
869 '255.240.0.0',
870 '255.248.0.0',
871 '255.252.0.0',
872 '255.254.0.0',
873 '255.255.0.0',
874 '255.255.128.0',
875 '255.255.192.0',
876 '255.255.224.0',
877 '255.255.240.0',
878 '255.255.248.0',
879 '255.255.252.0',
880 '255.255.254.0',
881 '255.255.255.0',
882 '255.255.255.128',
883 '255.255.255.192',
884 '255.255.255.224',
885 '255.255.255.240',
886 '255.255.255.248',
887 '255.255.255.252',
888 '255.255.255.254',
889 '255.255.255.255',
890 ];
891
892 # Note: we cannot use Net:IP, because that only allows strict
893 # CIDR networks
894 sub parse_ipv4_cidr {
895 my ($cidr, $noerr) = @_;
896
897 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
898 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
899 }
900
901 return undef if $noerr;
902
903 die "unable to parse ipv4 address/mask\n";
904 }
905
906 sub lxc_conf_to_pve {
907 my ($vmid, $lxc_conf) = @_;
908
909 my $properties = json_config_properties();
910
911 my $conf = { digest => $lxc_conf->{digest} };
912
913 foreach my $k (keys %$properties) {
914
915 if ($k eq 'description') {
916 if (my $raw = $lxc_conf->{'pve.comment'}) {
917 $conf->{$k} = PVE::Tools::decode_text($raw);
918 }
919 } elsif ($k eq 'onboot') {
920 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
921 } elsif ($k eq 'startup') {
922 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
923 } elsif ($k eq 'hostname') {
924 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
925 } elsif ($k eq 'nameserver') {
926 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
927 } elsif ($k eq 'searchdomain') {
928 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
929 } elsif ($k eq 'memory') {
930 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
931 $conf->{$k} = int($value / (1024*1024));
932 }
933 } elsif ($k eq 'swap') {
934 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
935 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
936 $conf->{$k} = int(($value -$mem) / (1024*1024));
937 }
938 } elsif ($k eq 'cpulimit') {
939 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
940 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
941
942 if ($cfs_period_us && $cfs_quota_us) {
943 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
944 } else {
945 $conf->{$k} = 0;
946 }
947 } elsif ($k eq 'cpuunits') {
948 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
949 } elsif ($k eq 'disk') {
950 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
951 $lxc_conf->{'pve.disksize'} : 0;
952 } elsif ($k =~ m/^net\d$/) {
953 my $net = $lxc_conf->{$k};
954 next if !$net;
955 $conf->{$k} = print_lxc_network($net);
956 }
957 }
958
959 return $conf;
960 }
961
962 # verify and cleanup nameserver list (replace \0 with ' ')
963 sub verify_nameserver_list {
964 my ($nameserver_list) = @_;
965
966 my @list = ();
967 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
968 PVE::JSONSchema::pve_verify_ip($server);
969 push @list, $server;
970 }
971
972 return join(' ', @list);
973 }
974
975 sub verify_searchdomain_list {
976 my ($searchdomain_list) = @_;
977
978 my @list = ();
979 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
980 # todo: should we add checks for valid dns domains?
981 push @list, $server;
982 }
983
984 return join(' ', @list);
985 }
986
987 sub update_lxc_config {
988 my ($vmid, $conf, $running, $param, $delete) = @_;
989
990 my @nohotplug;
991
992 if (defined($delete)) {
993 foreach my $opt (@$delete) {
994 if ($opt eq 'hostname' || $opt eq 'memory') {
995 die "unable to delete required option '$opt'\n";
996 } elsif ($opt eq 'swap') {
997 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
998 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
999 } elsif ($opt eq 'description') {
1000 delete $conf->{'pve.comment'};
1001 } elsif ($opt eq 'onboot') {
1002 delete $conf->{'pve.onboot'};
1003 } elsif ($opt eq 'startup') {
1004 delete $conf->{'pve.startup'};
1005 } elsif ($opt eq 'nameserver') {
1006 delete $conf->{'pve.nameserver'};
1007 push @nohotplug, $opt;
1008 next if $running;
1009 } elsif ($opt eq 'searchdomain') {
1010 delete $conf->{'pve.searchdomain'};
1011 push @nohotplug, $opt;
1012 next if $running;
1013 } elsif ($opt =~ m/^net(\d)$/) {
1014 delete $conf->{$opt};
1015 next if !$running;
1016 my $netid = $1;
1017 PVE::Network::veth_delete("veth${vmid}.$netid");
1018 } else {
1019 die "implement me"
1020 }
1021 PVE::LXC::write_config($vmid, $conf) if $running;
1022 }
1023 }
1024
1025 foreach my $opt (keys %$param) {
1026 my $value = $param->{$opt};
1027 if ($opt eq 'hostname') {
1028 $conf->{'lxc.utsname'} = $value;
1029 } elsif ($opt eq 'onboot') {
1030 $conf->{'pve.onboot'} = $value ? 1 : 0;
1031 } elsif ($opt eq 'startup') {
1032 $conf->{'pve.startup'} = $value;
1033 } elsif ($opt eq 'nameserver') {
1034 my $list = verify_nameserver_list($value);
1035 $conf->{'pve.nameserver'} = $list;
1036 push @nohotplug, $opt;
1037 next if $running;
1038 } elsif ($opt eq 'searchdomain') {
1039 my $list = verify_searchdomain_list($value);
1040 $conf->{'pve.searchdomain'} = $list;
1041 push @nohotplug, $opt;
1042 next if $running;
1043 } elsif ($opt eq 'memory') {
1044 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
1045 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
1046 } elsif ($opt eq 'swap') {
1047 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
1048 $mem = $param->{memory}*1024*1024 if $param->{memory};
1049 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
1050 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
1051
1052 } elsif ($opt eq 'cpulimit') {
1053 if ($value > 0) {
1054 my $cfs_period_us = 100000;
1055 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
1056 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
1057 write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
1058 } else {
1059 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1060 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1061 write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
1062 }
1063 } elsif ($opt eq 'cpuunits') {
1064 $conf->{'lxc.cgroup.cpu.shares'} = $value;
1065 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
1066 } elsif ($opt eq 'description') {
1067 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
1068 } elsif ($opt eq 'disk') {
1069 $conf->{'pve.disksize'} = $value;
1070 push @nohotplug, $opt;
1071 next if $running;
1072 } elsif ($opt =~ m/^net(\d+)$/) {
1073 my $netid = $1;
1074 my $net = PVE::LXC::parse_lxc_network($value);
1075 my $oldnet = $conf->{$opt} ? $conf->{$opt} : undef;
1076 $net->{'veth.pair'} = "veth${vmid}.$netid";
1077 $conf->{$opt} = $net;
1078 next if !$running;
1079 update_net($vmid, $net, $oldnet, $netid);
1080 } else {
1081 die "implement me"
1082 }
1083 PVE::LXC::write_config($vmid, $conf) if $running;
1084 }
1085
1086 if ($running && scalar(@nohotplug)) {
1087 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1088 }
1089 }
1090
1091 sub get_primary_ips {
1092 my ($conf) = @_;
1093
1094 # return data from net0
1095
1096 my $net = $conf->{net0};
1097 return undef if !$net;
1098
1099 my $ipv4 = $net->{ip};
1100 $ipv4 =~ s!/\d+$!! if $ipv4;
1101 my $ipv6 = $net->{ip};
1102 $ipv6 =~ s!/\d+$!! if $ipv6;
1103
1104 return ($ipv4, $ipv6);
1105 }
1106
1107 sub destory_lxc_container {
1108 my ($storage_cfg, $vmid, $conf) = @_;
1109
1110 if (my $volid = $conf->{'pve.volid'}) {
1111
1112 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
1113 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1114
1115 PVE::Storage::vdisk_free($storage_cfg, $volid);
1116
1117 destroy_config($vmid);
1118
1119 } else {
1120 my $cmd = ['lxc-destroy', '-n', $vmid ];
1121
1122 PVE::Tools::run_command($cmd);
1123 }
1124 }
1125
1126 my $safe_num_ne = sub {
1127 my ($a, $b) = @_;
1128
1129 return 0 if !defined($a) && !defined($b);
1130 return 1 if !defined($a);
1131 return 1 if !defined($b);
1132
1133 return $a != $b;
1134 };
1135
1136 my $safe_string_ne = sub {
1137 my ($a, $b) = @_;
1138
1139 return 0 if !defined($a) && !defined($b);
1140 return 1 if !defined($a);
1141 return 1 if !defined($b);
1142
1143 return $a ne $b;
1144 };
1145
1146 sub update_net {
1147 my ($vmid, $newnet, $oldnet, $netid) = @_;
1148
1149 my $veth = $newnet->{'veth.pair'};
1150 my $vethpeer = $veth."p";
1151 my $eth = $newnet->{name};
1152
1153 if ($oldnet) {
1154 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1155 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
1156
1157 PVE::Network::veth_delete($veth);
1158 hotplug_net($vmid, $newnet);
1159
1160 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1161 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1162 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
1163
1164 PVE::Network::tap_unplug($veth);
1165 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1166 }
1167 } else {
1168 hotplug_net($vmid, $newnet);
1169 }
1170
1171 update_ipconfig($vmid, $eth, $oldnet, $newnet);
1172 }
1173
1174 sub hotplug_net {
1175 my ($vmid, $newnet) = @_;
1176
1177 my $veth = $newnet->{'veth.pair'};
1178 my $vethpeer = $veth."p";
1179 my $eth = $newnet->{name};
1180
1181 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1182 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1183
1184 #attach peer in container
1185 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1186 PVE::Tools::run_command($cmd);
1187
1188 #link up peer in container
1189 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1190 PVE::Tools::run_command($cmd);
1191 }
1192
1193 sub update_ipconfig {
1194 my ($vmid, $eth, $oldnet, $newnet) = @_;
1195
1196
1197 if (&$safe_string_ne($oldnet->{ip}, $newnet->{ip})) {
1198 my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'addr', 'add', $newnet->{ip}, 'dev', $eth ];
1199 PVE::Tools::run_command($cmd);
1200 }
1201
1202 if (&$safe_string_ne($oldnet->{gw}, $newnet->{gw})) {
1203 my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'route', 'add', 'default', 'via', $newnet->{gw} ];
1204 PVE::Tools::run_command($cmd);
1205 }
1206 #fix me : ip,gateway removal
1207 #fix me : ipv6
1208 #fix me : write /etc/network/interfaces ?
1209
1210 }
1211
1212 1;