]>
git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
df95c0732cfe0a1237349abd3a9a2787ab6c8ab2
9 use PVE
:: Cluster
qw(cfs_register_file cfs_read_file) ;
12 use PVE
:: JSONSchema
qw(get_standard_option) ;
13 use PVE
:: Tools
qw( $IPV6RE $IPV4RE ) ;
17 cfs_register_file
( '/lxc/' , \
& parse_lxc_config
, \
& write_lxc_config
);
19 PVE
:: JSONSchema
:: register_format
( 'pve-lxc-network' , \
& verify_lxc_network
);
20 sub verify_lxc_network
{
21 my ( $value, $noerr ) = @_ ;
23 return $value if parse_lxc_network
( $value );
25 return undef if $noerr ;
27 die "unable to parse network setting \n " ;
30 my $nodename = PVE
:: INotify
:: nodename
();
33 my ( $name, $value ) = @_ ;
35 if ( $value =~ m/^(\d+)(b|k|m|g)?$/i ) {
36 my ( $res, $unit ) = ( $1, lc ( $2 || 'b' ));
38 return $res if $unit eq 'b' ;
39 return $res*1024 if $unit eq 'k' ;
40 return $res*1024*1024 if $unit eq 'm' ;
41 return $res*1024*1024*1024 if $unit eq 'g' ;
47 my $valid_lxc_keys = {
48 'lxc.arch' => 'i386|x86|i686|x86_64|amd64' ,
56 'lxc.cgroup.memory.limit_in_bytes' => \
& parse_lxc_size
,
57 'lxc.cgroup.memory.memsw.limit_in_bytes' => \
& parse_lxc_size
,
58 'lxc.cgroup.cpu.cfs_period_us' => '\d+' ,
59 'lxc.cgroup.cpu.cfs_quota_us' => '\d+' ,
60 'lxc.cgroup.cpu.shares' => '\d+' ,
64 'lxc.mount.entry' => 1 ,
65 'lxc.mount.auto' => 1 ,
70 'lxc.haltsignal' => 1 ,
71 'lxc.rebootsignal' => 1 ,
72 'lxc.stopsignal' => 1 ,
75 'lxc.console.logfile' => 1 ,
81 'lxc.aa_profile' => 1 ,
82 'lxc.aa_allow_incomplete' => 1 ,
83 'lxc.se_context' => 1 ,
86 'lxc.environment' => 1 ,
90 'lxc.start.auto' => 1 ,
91 'lxc.start.delay' => 1 ,
92 'lxc.start.order' => 1 ,
96 'lxc.hook.pre-start' => 1 ,
97 'lxc.hook.pre-mount' => 1 ,
98 'lxc.hook.mount' => 1 ,
99 'lxc.hook.autodev' => 1 ,
100 'lxc.hook.start' => 1 ,
101 'lxc.hook.post-stop' => 1 ,
102 'lxc.hook.clone' => 1 ,
105 'pve.onboot' => '(0|1)' ,
106 'pve.startup' => sub {
107 my ( $name, $value ) = @_ ;
108 return PVE
:: JSONSchema
:: pve_verify_startup_order
( $value );
113 my $valid_lxc_network_keys = {
116 name
=> 1 , # ifname inside container
117 'veth.pair' => 1 , # ifname at host (eth${vmid}.X)
121 my $valid_pve_network_keys = {
131 my $lxc_array_configs = {
137 sub write_lxc_config
{
138 my ( $filename, $data ) = @_ ;
142 return $raw if ! $data ;
144 my $done_hash = { digest
=> 1 };
146 foreach my $k ( sort keys %$data ) {
147 next if $k !~ m/^lxc\./ ;
148 $done_hash ->{ $k } = 1 ;
149 $raw .= " $k = $data ->{ $k } \n " ;
152 foreach my $k ( sort keys %$data ) {
153 next if $k !~ m/^pve\./ ;
154 $done_hash ->{ $k } = 1 ;
155 $raw .= " $k = $data ->{ $k } \n " ;
158 foreach my $k ( sort keys %$data ) {
159 next if $k !~ m/^net\d+$/ ;
160 $done_hash ->{ $k } = 1 ;
161 my $net = $data ->{ $k };
162 $raw .= "lxc.network.type = $net ->{type} \n " ;
163 foreach my $subkey ( sort keys %$net ) {
164 next if $subkey eq 'type' ;
165 if ( $valid_lxc_network_keys ->{ $subkey }) {
166 $raw .= "lxc.network. $subkey = $net ->{ $subkey } \n " ;
167 } elsif ( $valid_pve_network_keys ->{ $subkey }) {
168 $raw .= "pve.network. $subkey = $net ->{ $subkey } \n " ;
170 die "found invalid network key ' $subkey '" ;
175 foreach my $k ( sort keys %$data ) {
176 next if $done_hash ->{ $k };
177 die "found un-written value in config - implement this!" ;
183 sub parse_lxc_option
{
184 my ( $name, $value ) = @_ ;
186 my $parser = $valid_lxc_keys ->{ $name };
188 die "inavlid key ' $name ' \n " if ! defined ( $parser );
190 if ( $parser eq '1' ) {
192 } elsif ( ref ( $parser )) {
193 my $res = & $parser ( $name, $value );
194 return $res if defined ( $res );
197 return $value if $value =~ m/^$parser$/ ;
200 die "unable to parse value ' $value ' for option ' $name ' \n " ;
203 sub parse_lxc_config
{
204 my ( $filename, $raw ) = @_ ;
206 return undef if ! defined ( $raw );
209 digest
=> Digest
:: SHA
:: sha1_hex
( $raw ),
212 $filename =~ m
| /lxc/ ( \d
+)/ config
$|
213 || die "got strange filename ' $filename '" ;
217 my $network_counter = 0 ;
218 my $network_list = [];
219 my $host_ifnames = {};
221 my $find_next_hostif_name = sub {
222 for ( my $i = 0 ; $i < 10 ; $i++ ) {
223 my $name = "veth${vmid}. $i " ;
224 if (! $host_ifnames ->{ $name }) {
225 $host_ifnames ->{ $name } = 1 ;
230 die "unable to find free host_ifname" ; # should not happen
233 my $push_network = sub {
236 push @{ $network_list }, $netconf ;
238 if ( my $netname = $netconf ->{ 'veth.pair' }) {
239 if ( $netname =~ m/^veth(\d+).(\d)$/ ) {
240 die "wrong vmid for network interface pair \n " if $1 != $vmid ;
241 my $host_ifnames ->{ $netname } = 1 ;
243 die "wrong network interface pair \n " ;
250 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
253 next if $line =~ m/^\#/ ;
254 next if $line =~ m/^\s*$/ ;
256 if ( $line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
257 my ( $subkey, $value ) = ( $1, $2 );
258 if ( $subkey eq 'type' ) {
259 & $push_network ( $network );
260 $network = { type
=> $value };
261 } elsif ( $valid_lxc_network_keys ->{ $subkey }) {
262 $network ->{ $subkey } = $value ;
264 die "unable to parse config line: $line\n " ;
268 if ( $line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
269 my ( $subkey, $value ) = ( $1, $2 );
270 if ( $valid_pve_network_keys ->{ $subkey }) {
271 $network ->{ $subkey } = $value ;
273 die "unable to parse config line: $line\n " ;
277 if ( $line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/ ) {
278 my ( $name, $value ) = ( $1, $2 );
279 $data ->{ $name } = $value ;
282 if ( $line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/ ) {
283 my ( $name, $value ) = ( $1, $2 );
285 die "multiple definitions for $name\n " if defined ( $data ->{ $name });
287 $data ->{ $name } = parse_lxc_option
( $name, $value );
291 die "unable to parse config line: $line\n " ;
294 & $push_network ( $network );
296 foreach my $net (@{ $network_list }) {
297 $net ->{ 'veth.pair' } = & $find_next_hostif_name () if ! $net ->{ 'veth.pair' };
298 $net ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $net ->{ hwaddr
};
299 die "unsupported network type ' $net ->{type}' \n " if $net ->{ type
} ne 'veth' ;
301 if ( $net ->{ 'veth.pair' } =~ m/^veth\d+.(\d+)$/ ) {
302 $data ->{ "net $1 " } = $net ;
310 my $vmlist = PVE
:: Cluster
:: get_vmlist
();
312 return $res if ! $vmlist || ! $vmlist ->{ ids
};
313 my $ids = $vmlist ->{ ids
};
315 foreach my $vmid ( keys %$ids ) {
316 next if ! $vmid ; # skip CT0
317 my $d = $ids ->{ $vmid };
318 next if ! $d ->{ node
} || $d ->{ node
} ne $nodename ;
319 next if ! $d ->{ type
} || $d ->{ type
} ne 'lxc' ;
320 $res ->{ $vmid }->{ type
} = 'lxc' ;
325 sub cfs_config_path
{
326 my ( $vmid, $node ) = @_ ;
328 $node = $nodename if ! $node ;
329 return "nodes/ $node/lxc/$vmid/config " ;
333 my ( $vmid, $node ) = @_ ;
335 my $cfspath = cfs_config_path
( $vmid, $node );
336 return "/etc/pve/ $cfspath " ;
342 my $cfspath = cfs_config_path
( $vmid );
344 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath );
345 die "container $vmid does not exists \n " if ! defined ( $conf );
351 my ( $vmid, $conf ) = @_ ;
353 my $cfspath = cfs_config_path
( $vmid );
355 PVE
:: Cluster
:: cfs_write_file
( $cfspath, $conf );
359 sub write_temp_config
{
360 my ( $vmid, $conf ) = @_ ;
363 my $filename = "/tmp/temp-lxc-conf- $vmid - $$ - $tempcounter .conf" ;
365 my $raw = write_lxc_config
( $filename, $conf );
367 PVE
:: Tools
:: file_set_contents
( $filename, $raw );
373 my ( $vmid, $timeout, $code, @param ) = @_ ;
375 my $lockdir = "/run/lock/lxc" ;
376 my $lockfile = " $lockdir/pve -config-{ $vmid }.lock" ;
378 File
:: Path
:: make_path
( $lockdir );
380 my $res = PVE
:: Tools
:: lock_file
( $lockfile, $timeout, $code, @param );
391 description
=> "Specifies whether a VM will be started during system bootup." ,
394 startup
=> get_standard_option
( 'pve-startup-order' ),
398 description
=> "The number of CPUs for this container (0 is unlimited)." ,
406 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." ,
414 description
=> "Amount of RAM for the VM in MB." ,
421 description
=> "Amount of SWAP for the VM in MB." ,
428 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits." ,
434 description
=> "Set a host name for the container." ,
441 description
=> "Container description. Only used on the configuration web interface." ,
446 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver." ,
451 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." ,
455 my $MAX_LXC_NETWORKS = 10 ;
456 for ( my $i = 0 ; $i < $MAX_LXC_NETWORKS ; $i++ ) {
457 $confdesc ->{ "net $i " } = {
459 type
=> 'string' , format
=> 'pve-lxc-network' ,
460 description
=> "Specifies network interfaces for the container." ,
467 return defined ( $confdesc ->{ $name });
470 # add JSON properties for create and set function
471 sub json_config_properties
{
474 foreach my $opt ( keys %$confdesc ) {
475 $prop ->{ $opt } = $confdesc ->{ $opt };
481 # container status helpers
483 sub list_active_containers
{
485 my $filename = "/proc/net/unix" ;
487 # similar test is used by lcxcontainers.c: list_active_containers
490 my $fh = IO
:: File-
> new ( $filename, "r" );
493 while ( defined ( my $line = < $fh >)) {
494 if ( $line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/ ) {
496 if ( $path =~ m!^@/etc/pve/lxc/(\d+)/command$! ) {
507 # warning: this is slow
511 my $active_hash = list_active_containers
();
513 return 1 if defined ( $active_hash ->{ $vmid });
521 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
523 my $active_hash = list_active_containers
();
525 foreach my $vmid ( keys %$list ) {
526 my $d = $list ->{ $vmid };
527 $d ->{ status
} = $active_hash ->{ $vmid } ?
'running' : 'stopped' ;
529 my $cfspath = cfs_config_path
( $vmid );
530 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath ) || {};
532 $d ->{ name
} = $conf ->{ 'lxc.utsname' } || "CT $vmid " ;
533 $d ->{ name
} =~ s/[\s]//g ;
537 my $cfs_period_us = $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
538 my $cfs_quota_us = $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
540 if ( $cfs_period_us && $cfs_quota_us ) {
541 $d ->{ cpus
} = int ( $cfs_quota_us/$cfs_period_us );
546 if ( my $private = $conf ->{ 'lxc.rootfs' }) {
547 my $res = PVE
:: Tools
:: df
( $private, 2 );
548 $d ->{ disk
} = $res ->{ used
};
549 $d ->{ maxdisk
} = $res ->{ total
};
554 $d ->{ maxmem
} = ( $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }|| 0 ) +
555 ( $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }|| 0 );
567 foreach my $vmid ( keys %$list ) {
568 my $d = $list ->{ $vmid };
569 next if $d ->{ status
} ne 'running' ;
571 $d ->{ uptime
} = 100 ; # fixme:
573 $d ->{ mem
} = read_cgroup_value
( 'memory' , $vmid, 'memory.usage_in_bytes' );
574 $d ->{ swap
} = read_cgroup_value
( 'memory' , $vmid, 'memory.memsw.usage_in_bytes' ) - $d ->{ mem
};
581 sub print_lxc_network
{
584 die "no network bridge defined \n " if ! $net ->{ bridge
};
586 my $res = "bridge= $net ->{bridge}" ;
588 foreach my $k ( qw(hwaddr mtu name ip gw ip6 gw6 firewall tag) ) {
589 next if ! defined ( $net ->{ $k });
590 $res .= ", $k = $net ->{ $k }" ;
596 sub parse_lxc_network
{
601 return $res if ! $data ;
603 foreach my $pv ( split ( /,/ , $data )) {
604 if ( $pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/ ) {
611 $res ->{ type
} = 'veth' ;
612 $res ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $res ->{ mac
};
617 sub read_cgroup_value
{
618 my ( $group, $vmid, $name, $full ) = @_ ;
620 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
622 return PVE
:: Tools
:: file_get_contents
( $path ) if $full ;
624 return PVE
:: Tools
:: file_read_firstline
( $path );
627 sub find_lxc_console_pids
{
631 PVE
:: Tools
:: dir_glob_foreach
( '/proc' , '\d+' , sub {
634 my $cmdline = PVE
:: Tools
:: file_read_firstline
( "/proc/ $pid/cmdline " );
637 my @args = split ( /\0/ , $cmdline );
639 # serach for lxc-console -n <vmid>
640 return if scalar ( @args ) != 3 ;
641 return if $args [ 1 ] ne '-n' ;
642 return if $args [ 2 ] !~ m/^\d+$/ ;
643 return if $args [ 0 ] !~ m
|^( /usr/ bin
/) ?lxc-console
$|;
647 push @{ $res ->{ $vmid }}, $pid ;
653 my $ipv4_reverse_mask = [
689 # Note: we cannot use Net:IP, because that only allows strict
691 sub parse_ipv4_cidr
{
692 my ( $cidr, $noerr ) = @_ ;
694 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
695 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
698 return undef if $noerr ;
700 die "unable to parse ipv4 address/mask \n " ;
703 sub lxc_conf_to_pve
{
704 my ( $vmid, $lxc_conf ) = @_ ;
706 my $properties = json_config_properties
();
708 my $conf = { digest
=> $lxc_conf ->{ digest
} };
710 foreach my $k ( keys %$properties ) {
712 if ( $k eq 'description' ) {
713 if ( my $raw = $lxc_conf ->{ 'pve.comment' }) {
714 $conf ->{ $k } = PVE
:: Tools
:: decode_text
( $raw );
716 } elsif ( $k eq 'onboot' ) {
717 $conf ->{ $k } = $lxc_conf ->{ 'pve.onboot' } if $lxc_conf ->{ 'pve.onboot' };
718 } elsif ( $k eq 'startup' ) {
719 $conf ->{ $k } = $lxc_conf ->{ 'pve.startup' } if $lxc_conf ->{ 'pve.startup' };
720 } elsif ( $k eq 'hostname' ) {
721 $conf ->{ $k } = $lxc_conf ->{ 'lxc.utsname' } if $lxc_conf ->{ 'lxc.utsname' };
722 } elsif ( $k eq 'memory' ) {
723 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }) {
724 $conf ->{ $k } = int ( $value / ( 1024 * 1024 ));
726 } elsif ( $k eq 'swap' ) {
727 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }) {
728 my $mem = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } || 0 ;
729 $conf ->{ $k } = int (( $value - $mem ) / ( 1024 * 1024 ));
731 } elsif ( $k eq 'cpus' ) {
732 my $cfs_period_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
733 my $cfs_quota_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
735 if ( $cfs_period_us && $cfs_quota_us ) {
736 $conf ->{ $k } = int ( $cfs_quota_us/$cfs_period_us );
740 } elsif ( $k eq 'cpuunits' ) {
741 $conf ->{ $k } = $lxc_conf ->{ 'lxc.cgroup.cpu.shares' } || 1024 ;
742 } elsif ( $k =~ m/^net\d$/ ) {
743 my $net = $lxc_conf ->{ $k };
745 $conf ->{ $k } = print_lxc_network
( $net );
752 sub update_lxc_config
{
753 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
756 die "unable to modify config while container is running \n " if $running ;
758 if ( defined ( $delete )) {
759 foreach my $opt ( @$delete ) {
760 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
761 die "unable to delete required option ' $opt ' \n " ;
762 } elsif ( $opt eq 'swap' ) {
763 delete $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' };
764 } elsif ( $opt eq 'description' ) {
765 delete $conf ->{ 'pve.comment' };
766 } elsif ( $opt eq 'onboot' ) {
767 delete $conf ->{ 'pve.onboot' };
768 } elsif ( $opt eq 'startup' ) {
769 delete $conf ->{ 'pve.startup' };
770 } elsif ( $opt =~ m/^net\d$/ ) {
771 delete $conf ->{ $opt };
778 foreach my $opt ( keys %$param ) {
779 my $value = $param ->{ $opt };
780 if ( $opt eq 'hostname' ) {
781 $conf ->{ 'lxc.utsname' } = $value ;
782 } elsif ( $opt eq 'onboot' ) {
783 $conf ->{ 'pve.onboot' } = $value ?
1 : 0 ;
784 } elsif ( $opt eq 'startup' ) {
785 $conf ->{ 'pve.startup' } = $value ;
786 } elsif ( $opt eq 'memory' ) {
787 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
788 } elsif ( $opt eq 'swap' ) {
789 my $mem = $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' };
790 $mem = $param ->{ memory
}* 1024 * 1024 if $param ->{ memory
};
791 $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' } = $mem + $value*1024*1024 ;
792 } elsif ( $opt eq 'cpus' ) {
794 my $cfs_period_us = 100000 ;
795 $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' } = $cfs_period_us ;
796 $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' } = $cfs_period_us*$value ;
798 delete $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
799 delete $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
801 } elsif ( $opt eq 'cpuunits' ) {
802 $conf ->{ 'lxc.cgroup.cpu.shares' } = $value ;
803 } elsif ( $opt eq 'description' ) {
804 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
805 } elsif ( $opt =~ m/^net(\d+)$/ ) {
807 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
808 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
809 $conf ->{ $opt } = $net ;