]>
git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
9 use PVE
:: Cluster
qw(cfs_register_file cfs_read_file) ;
12 use PVE
:: Tools
qw( $IPV6RE $IPV4RE ) ;
16 cfs_register_file
( '/lxc/' , \
& parse_lxc_config
, \
& write_lxc_config
);
18 PVE
:: JSONSchema
:: register_format
( 'pve-lxc-network' , \
& verify_lxc_network
);
19 sub verify_lxc_network
{
20 my ( $value, $noerr ) = @_ ;
22 return $value if parse_lxc_network
( $value );
24 return undef if $noerr ;
26 die "unable to parse network setting \n " ;
29 my $nodename = PVE
:: INotify
:: nodename
();
32 my ( $name, $value ) = @_ ;
34 if ( $value =~ m/^(\d+)(b|k|m|g)?$/i ) {
35 my ( $res, $unit ) = ( $1, lc ( $2 || 'b' ));
37 return $res if $unit eq 'b' ;
38 return $res*1024 if $unit eq 'k' ;
39 return $res*1024*1024 if $unit eq 'm' ;
40 return $res*1024*1024*1024 if $unit eq 'g' ;
46 my $valid_lxc_keys = {
47 'lxc.arch' => 'i386|x86|i686|x86_64|amd64' ,
55 'lxc.cgroup.memory.limit_in_bytes' => \
& parse_lxc_size
,
56 'lxc.cgroup.memory.memsw.limit_in_bytes' => \
& parse_lxc_size
,
57 'lxc.cgroup.cpu.cfs_period_us' => '\d+' ,
58 'lxc.cgroup.cpu.cfs_quota_us' => '\d+' ,
59 'lxc.cgroup.cpu.shares' => '\d+' ,
63 'lxc.mount.entry' => 1 ,
64 'lxc.mount.auto' => 1 ,
69 'lxc.haltsignal' => 1 ,
70 'lxc.rebootsignal' => 1 ,
71 'lxc.stopsignal' => 1 ,
74 'lxc.console.logfile' => 1 ,
80 'lxc.aa_profile' => 1 ,
81 'lxc.aa_allow_incomplete' => 1 ,
82 'lxc.se_context' => 1 ,
85 'lxc.environment' => 1 ,
89 'lxc.start.auto' => 1 ,
90 'lxc.start.delay' => 1 ,
91 'lxc.start.order' => 1 ,
95 'lxc.hook.pre-start' => 1 ,
96 'lxc.hook.pre-mount' => 1 ,
97 'lxc.hook.mount' => 1 ,
98 'lxc.hook.autodev' => 1 ,
99 'lxc.hook.start' => 1 ,
100 'lxc.hook.post-stop' => 1 ,
101 'lxc.hook.clone' => 1 ,
104 'pve.onboot' => '(0|1)' ,
108 my $valid_lxc_network_keys = {
111 name
=> 1 , # ifname inside container
112 'veth.pair' => 1 , # ifname at host (eth${vmid}.X)
116 my $valid_pve_network_keys = {
126 my $lxc_array_configs = {
132 sub write_lxc_config
{
133 my ( $filename, $data ) = @_ ;
137 return $raw if ! $data ;
139 my $done_hash = { digest
=> 1 };
141 foreach my $k ( sort keys %$data ) {
142 next if $k !~ m/^lxc\./ ;
143 $done_hash ->{ $k } = 1 ;
144 $raw .= " $k = $data ->{ $k } \n " ;
147 foreach my $k ( sort keys %$data ) {
148 next if $k !~ m/^pve\./ ;
149 $done_hash ->{ $k } = 1 ;
150 $raw .= " $k = $data ->{ $k } \n " ;
153 foreach my $k ( sort keys %$data ) {
154 next if $k !~ m/^net\d+$/ ;
155 $done_hash ->{ $k } = 1 ;
156 my $net = $data ->{ $k };
157 $raw .= "lxc.network.type = $net ->{type} \n " ;
158 foreach my $subkey ( sort keys %$net ) {
159 next if $subkey eq 'type' ;
160 if ( $valid_lxc_network_keys ->{ $subkey }) {
161 $raw .= "lxc.network. $subkey = $net ->{ $subkey } \n " ;
162 } elsif ( $valid_pve_network_keys ->{ $subkey }) {
163 $raw .= "pve.network. $subkey = $net ->{ $subkey } \n " ;
165 die "found invalid network key ' $subkey '" ;
170 foreach my $k ( sort keys %$data ) {
171 next if $done_hash ->{ $k };
172 die "found un-written value in config - implement this!" ;
178 sub parse_lxc_option
{
179 my ( $name, $value ) = @_ ;
181 my $parser = $valid_lxc_keys ->{ $name };
183 die "inavlid key ' $name ' \n " if ! defined ( $parser );
185 if ( $parser eq '1' ) {
187 } elsif ( ref ( $parser )) {
188 my $res = & $parser ( $name, $value );
189 return $res if defined ( $res );
192 return $value if $value =~ m/^$parser$/ ;
195 die "unable to parse value ' $value ' for option ' $name ' \n " ;
198 sub parse_lxc_config
{
199 my ( $filename, $raw ) = @_ ;
201 return undef if ! defined ( $raw );
204 digest
=> Digest
:: SHA
:: sha1_hex
( $raw ),
207 $filename =~ m
| /lxc/ ( \d
+)/ config
$|
208 || die "got strange filename ' $filename '" ;
212 my $network_counter = 0 ;
213 my $network_list = [];
214 my $host_ifnames = {};
216 my $find_next_hostif_name = sub {
217 for ( my $i = 0 ; $i < 10 ; $i++ ) {
218 my $name = "veth${vmid}. $i " ;
219 if (! $host_ifnames ->{ $name }) {
220 $host_ifnames ->{ $name } = 1 ;
225 die "unable to find free host_ifname" ; # should not happen
228 my $push_network = sub {
231 push @{ $network_list }, $netconf ;
233 if ( my $netname = $netconf ->{ 'veth.pair' }) {
234 if ( $netname =~ m/^veth(\d+).(\d)$/ ) {
235 die "wrong vmid for network interface pair \n " if $1 != $vmid ;
236 my $host_ifnames ->{ $netname } = 1 ;
238 die "wrong network interface pair \n " ;
245 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
248 next if $line =~ m/^\#/ ;
249 next if $line =~ m/^\s*$/ ;
251 if ( $line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
252 my ( $subkey, $value ) = ( $1, $2 );
253 if ( $subkey eq 'type' ) {
254 & $push_network ( $network );
255 $network = { type
=> $value };
256 } elsif ( $valid_lxc_network_keys ->{ $subkey }) {
257 $network ->{ $subkey } = $value ;
259 die "unable to parse config line: $line\n " ;
263 if ( $line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
264 my ( $subkey, $value ) = ( $1, $2 );
265 if ( $valid_pve_network_keys ->{ $subkey }) {
266 $network ->{ $subkey } = $value ;
268 die "unable to parse config line: $line\n " ;
272 if ( $line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/ ) {
273 my ( $name, $value ) = ( $1, $2 );
274 $data ->{ $name } = $value ;
277 if ( $line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/ ) {
278 my ( $name, $value ) = ( $1, $2 );
280 die "multiple definitions for $name\n " if defined ( $data ->{ $name });
282 $data ->{ $name } = parse_lxc_option
( $name, $value );
286 die "unable to parse config line: $line\n " ;
289 & $push_network ( $network );
291 foreach my $net (@{ $network_list }) {
292 $net ->{ 'veth.pair' } = & $find_next_hostif_name () if ! $net ->{ 'veth.pair' };
293 $net ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $net ->{ hwaddr
};
294 die "unsupported network type ' $net ->{type}' \n " if $net ->{ type
} ne 'veth' ;
296 if ( $net ->{ 'veth.pair' } =~ m/^veth\d+.(\d+)$/ ) {
297 $data ->{ "net $1 " } = $net ;
305 my $vmlist = PVE
:: Cluster
:: get_vmlist
();
307 return $res if ! $vmlist || ! $vmlist ->{ ids
};
308 my $ids = $vmlist ->{ ids
};
310 foreach my $vmid ( keys %$ids ) {
311 next if ! $vmid ; # skip CT0
312 my $d = $ids ->{ $vmid };
313 next if ! $d ->{ node
} || $d ->{ node
} ne $nodename ;
314 next if ! $d ->{ type
} || $d ->{ type
} ne 'lxc' ;
315 $res ->{ $vmid }->{ type
} = 'lxc' ;
320 sub cfs_config_path
{
321 my ( $vmid, $node ) = @_ ;
323 $node = $nodename if ! $node ;
324 return "nodes/ $node/lxc/$vmid/config " ;
328 my ( $vmid, $node ) = @_ ;
330 my $cfspath = cfs_config_path
( $vmid, $node );
331 return "/etc/pve/ $cfspath " ;
337 my $cfspath = cfs_config_path
( $vmid );
339 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath );
340 die "container $vmid does not exists \n " if ! defined ( $conf );
346 my ( $vmid, $conf ) = @_ ;
348 my $cfspath = cfs_config_path
( $vmid );
350 PVE
:: Cluster
:: cfs_write_file
( $cfspath, $conf );
354 sub write_temp_config
{
355 my ( $vmid, $conf ) = @_ ;
358 my $filename = "/tmp/temp-lxc-conf- $vmid - $$ - $tempcounter .conf" ;
360 my $raw = write_lxc_config
( $filename, $conf );
362 PVE
:: Tools
:: file_set_contents
( $filename, $raw );
368 my ( $vmid, $timeout, $code, @param ) = @_ ;
370 my $lockdir = "/run/lock/lxc" ;
371 my $lockfile = " $lockdir/pve -config-{ $vmid }.lock" ;
373 File
:: Path
:: make_path
( $lockdir );
375 my $res = PVE
:: Tools
:: lock_file
( $lockfile, $timeout, $code, @param );
386 description
=> "Specifies whether a VM will be started during system bootup." ,
392 description
=> "The number of CPUs for this container (0 is unlimited)." ,
400 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\n NOTE: You can disable fair-scheduler configuration by setting this to 0." ,
408 description
=> "Amount of RAM for the VM in MB." ,
415 description
=> "Amount of SWAP for the VM in MB." ,
422 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits." ,
428 description
=> "Set a host name for the container." ,
435 description
=> "Container description. Only used on the configuration web interface." ,
440 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver." ,
445 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." ,
449 my $MAX_LXC_NETWORKS = 10 ;
450 for ( my $i = 0 ; $i < $MAX_LXC_NETWORKS ; $i++ ) {
451 $confdesc ->{ "net $i " } = {
453 type
=> 'string' , format
=> 'pve-lxc-network' ,
454 description
=> "Specifies network interfaces for the container." ,
461 return defined ( $confdesc ->{ $name });
464 # add JSON properties for create and set function
465 sub json_config_properties
{
468 foreach my $opt ( keys %$confdesc ) {
469 $prop ->{ $opt } = $confdesc ->{ $opt };
475 # container status helpers
477 sub list_active_containers
{
479 my $filename = "/proc/net/unix" ;
481 # similar test is used by lcxcontainers.c: list_active_containers
484 my $fh = IO
:: File-
> new ( $filename, "r" );
487 while ( defined ( my $line = < $fh >)) {
488 if ( $line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/ ) {
490 if ( $path =~ m!^@/etc/pve/lxc/(\d+)/command$! ) {
501 # warning: this is slow
505 my $active_hash = list_active_containers
();
507 return 1 if defined ( $active_hash ->{ $vmid });
515 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
517 my $active_hash = list_active_containers
();
519 foreach my $vmid ( keys %$list ) {
520 my $d = $list ->{ $vmid };
521 $d ->{ status
} = $active_hash ->{ $vmid } ?
'running' : 'stopped' ;
523 my $cfspath = cfs_config_path
( $vmid );
524 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath ) || {};
526 $d ->{ name
} = $conf ->{ 'lxc.utsname' } || "CT $vmid " ;
527 $d ->{ name
} =~ s/[\s]//g ;
531 my $cfs_period_us = $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
532 my $cfs_quota_us = $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
534 if ( $cfs_period_us && $cfs_quota_us ) {
535 $d ->{ cpus
} = int ( $cfs_quota_us/$cfs_period_us );
540 if ( my $private = $conf ->{ 'lxc.rootfs' }) {
541 my $res = PVE
:: Tools
:: df
( $private, 2 );
542 $d ->{ disk
} = $res ->{ used
};
543 $d ->{ maxdisk
} = $res ->{ total
};
548 $d ->{ maxmem
} = ( $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }|| 0 ) +
549 ( $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }|| 0 );
561 foreach my $vmid ( keys %$list ) {
562 my $d = $list ->{ $vmid };
563 next if $d ->{ status
} ne 'running' ;
565 $d ->{ uptime
} = 100 ; # fixme:
567 $d ->{ mem
} = read_cgroup_value
( 'memory' , $vmid, 'memory.usage_in_bytes' );
568 $d ->{ swap
} = read_cgroup_value
( 'memory' , $vmid, 'memory.memsw.usage_in_bytes' ) - $d ->{ mem
};
575 sub print_lxc_network
{
578 die "no network bridge defined \n " if ! $net ->{ bridge
};
580 my $res = "bridge= $net ->{bridge}" ;
582 foreach my $k ( qw(hwaddr mtu name ip gw ip6 gw6 firewall tag) ) {
583 next if ! defined ( $net ->{ $k });
584 $res .= ", $k = $net ->{ $k }" ;
590 sub parse_lxc_network
{
595 return $res if ! $data ;
597 foreach my $pv ( split ( /,/ , $data )) {
598 if ( $pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/ ) {
605 $res ->{ type
} = 'veth' ;
606 $res ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $res ->{ mac
};
611 sub read_cgroup_value
{
612 my ( $group, $vmid, $name, $full ) = @_ ;
614 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
616 return PVE
:: Tools
:: file_get_contents
( $path ) if $full ;
618 return PVE
:: Tools
:: file_read_firstline
( $path );
621 sub find_lxc_console_pids
{
625 PVE
:: Tools
:: dir_glob_foreach
( '/proc' , '\d+' , sub {
628 my $cmdline = PVE
:: Tools
:: file_read_firstline
( "/proc/ $pid/cmdline " );
631 my @args = split ( /\0/ , $cmdline );
633 # serach for lxc-console -n <vmid>
634 return if scalar ( @args ) != 3 ;
635 return if $args [ 1 ] ne '-n' ;
636 return if $args [ 2 ] !~ m/^\d+$/ ;
637 return if $args [ 0 ] !~ m
|^( /usr/ bin
/) ?lxc-console
$|;
641 push @{ $res ->{ $vmid }}, $pid ;
647 my $ipv4_reverse_mask = [
683 # Note: we cannot use Net:IP, because that only allows strict
685 sub parse_ipv4_cidr
{
686 my ( $cidr, $noerr ) = @_ ;
688 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
689 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
692 return undef if $noerr ;
694 die "unable to parse ipv4 address/mask \n " ;
697 sub lxc_conf_to_pve
{
698 my ( $vmid, $lxc_conf ) = @_ ;
700 my $properties = json_config_properties
();
702 my $conf = { digest
=> $lxc_conf ->{ digest
} };
704 foreach my $k ( keys %$properties ) {
706 if ( $k eq 'description' ) {
707 if ( my $raw = $lxc_conf ->{ 'pve.comment' }) {
708 $conf ->{ $k } = PVE
:: Tools
:: decode_text
( $raw );
710 } elsif ( $k eq 'onboot' ) {
711 $conf ->{ $k } = $lxc_conf ->{ 'pve.onboot' } if $lxc_conf ->{ 'pve.onboot' };
712 } elsif ( $k eq 'hostname' ) {
713 $conf ->{ $k } = $lxc_conf ->{ 'lxc.utsname' } if $lxc_conf ->{ 'lxc.utsname' };
714 } elsif ( $k eq 'memory' ) {
715 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }) {
716 $conf ->{ $k } = int ( $value / ( 1024 * 1024 ));
718 } elsif ( $k eq 'swap' ) {
719 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }) {
720 my $mem = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } || 0 ;
721 $conf ->{ $k } = int (( $value - $mem ) / ( 1024 * 1024 ));
723 } elsif ( $k eq 'cpus' ) {
724 my $cfs_period_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
725 my $cfs_quota_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
727 if ( $cfs_period_us && $cfs_quota_us ) {
728 $conf ->{ $k } = int ( $cfs_quota_us/$cfs_period_us );
732 } elsif ( $k eq 'cpuunits' ) {
733 $conf ->{ $k } = $lxc_conf ->{ 'lxc.cgroup.cpu.shares' } || 1024 ;
734 } elsif ( $k =~ m/^net\d$/ ) {
735 my $net = $lxc_conf ->{ $k };
737 $conf ->{ $k } = print_lxc_network
( $net );
744 sub update_lxc_config
{
745 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
748 die "unable to modify config while container is running \n " if $running ;
750 if ( defined ( $delete )) {
751 foreach my $opt ( @$delete ) {
752 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
753 die "unable to delete required option ' $opt ' \n " ;
754 } elsif ( $opt eq 'swap' ) {
755 delete $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' };
756 } elsif ( $opt eq 'description' ) {
757 delete $conf ->{ 'pve.comment' };
758 } elsif ( $opt eq 'onboot' ) {
759 delete $conf ->{ 'pve.onboot' };
760 } elsif ( $opt =~ m/^net\d$/ ) {
761 delete $conf ->{ $opt };
768 foreach my $opt ( keys %$param ) {
769 my $value = $param ->{ $opt };
770 if ( $opt eq 'hostname' ) {
771 $conf ->{ 'lxc.utsname' } = $value ;
772 } elsif ( $opt eq 'onboot' ) {
773 $conf ->{ 'pve.onboot' } = $value ?
1 : 0 ;
774 } elsif ( $opt eq 'memory' ) {
775 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
776 } elsif ( $opt eq 'swap' ) {
777 my $mem = $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' };
778 $mem = $param ->{ memory
}* 1024 * 1024 if $param ->{ memory
};
779 $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' } = $mem + $value*1024*1024 ;
780 } elsif ( $opt eq 'cpus' ) {
782 my $cfs_period_us = 100000 ;
783 $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' } = $cfs_period_us ;
784 $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' } = $cfs_period_us*$value ;
786 delete $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
787 delete $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
789 } elsif ( $opt eq 'cpuunits' ) {
790 $conf ->{ 'lxc.cgroup.cpu.shares' } = $value ;
791 } elsif ( $opt eq 'description' ) {
792 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
793 } elsif ( $opt =~ m/^net(\d+)$/ ) {
795 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
796 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
797 $conf ->{ $opt } = $net ;