]>
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
:: 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.nameserver' => 1 ,
106 'pve.searchdomain' => 1 ,
107 'pve.onboot' => '(0|1)' ,
108 'pve.startup' => sub {
109 my ( $name, $value ) = @_ ;
110 return PVE
:: JSONSchema
:: pve_verify_startup_order
( $value );
115 my $valid_lxc_network_keys = {
118 name
=> 1 , # ifname inside container
119 'veth.pair' => 1 , # ifname at host (eth${vmid}.X)
123 my $valid_pve_network_keys = {
133 my $lxc_array_configs = {
139 sub write_lxc_config
{
140 my ( $filename, $data ) = @_ ;
144 return $raw if ! $data ;
146 my $done_hash = { digest
=> 1 };
148 foreach my $k ( sort keys %$data ) {
149 next if $k !~ m/^lxc\./ ;
150 $done_hash ->{ $k } = 1 ;
151 $raw .= " $k = $data ->{ $k } \n " ;
154 foreach my $k ( sort keys %$data ) {
155 next if $k !~ m/^pve\./ ;
156 $done_hash ->{ $k } = 1 ;
157 $raw .= " $k = $data ->{ $k } \n " ;
160 foreach my $k ( sort keys %$data ) {
161 next if $k !~ m/^net\d+$/ ;
162 $done_hash ->{ $k } = 1 ;
163 my $net = $data ->{ $k };
164 $raw .= "lxc.network.type = $net ->{type} \n " ;
165 foreach my $subkey ( sort keys %$net ) {
166 next if $subkey eq 'type' ;
167 if ( $valid_lxc_network_keys ->{ $subkey }) {
168 $raw .= "lxc.network. $subkey = $net ->{ $subkey } \n " ;
169 } elsif ( $valid_pve_network_keys ->{ $subkey }) {
170 $raw .= "pve.network. $subkey = $net ->{ $subkey } \n " ;
172 die "found invalid network key ' $subkey '" ;
177 foreach my $k ( sort keys %$data ) {
178 next if $done_hash ->{ $k };
179 die "found un-written value in config - implement this!" ;
185 sub parse_lxc_option
{
186 my ( $name, $value ) = @_ ;
188 my $parser = $valid_lxc_keys ->{ $name };
190 die "inavlid key ' $name ' \n " if ! defined ( $parser );
192 if ( $parser eq '1' ) {
194 } elsif ( ref ( $parser )) {
195 my $res = & $parser ( $name, $value );
196 return $res if defined ( $res );
199 return $value if $value =~ m/^$parser$/ ;
202 die "unable to parse value ' $value ' for option ' $name ' \n " ;
205 sub parse_lxc_config
{
206 my ( $filename, $raw ) = @_ ;
208 return undef if ! defined ( $raw );
211 digest
=> Digest
:: SHA
:: sha1_hex
( $raw ),
214 $filename =~ m
| /lxc/ ( \d
+)/ config
$|
215 || die "got strange filename ' $filename '" ;
219 my $network_counter = 0 ;
220 my $network_list = [];
221 my $host_ifnames = {};
223 my $find_next_hostif_name = sub {
224 for ( my $i = 0 ; $i < 10 ; $i++ ) {
225 my $name = "veth${vmid}. $i " ;
226 if (! $host_ifnames ->{ $name }) {
227 $host_ifnames ->{ $name } = 1 ;
232 die "unable to find free host_ifname" ; # should not happen
235 my $push_network = sub {
238 push @{ $network_list }, $netconf ;
240 if ( my $netname = $netconf ->{ 'veth.pair' }) {
241 if ( $netname =~ m/^veth(\d+).(\d)$/ ) {
242 die "wrong vmid for network interface pair \n " if $1 != $vmid ;
243 my $host_ifnames ->{ $netname } = 1 ;
245 die "wrong network interface pair \n " ;
252 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
255 next if $line =~ m/^\#/ ;
256 next if $line =~ m/^\s*$/ ;
258 if ( $line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
259 my ( $subkey, $value ) = ( $1, $2 );
260 if ( $subkey eq 'type' ) {
261 & $push_network ( $network );
262 $network = { type
=> $value };
263 } elsif ( $valid_lxc_network_keys ->{ $subkey }) {
264 $network ->{ $subkey } = $value ;
266 die "unable to parse config line: $line\n " ;
270 if ( $line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
271 my ( $subkey, $value ) = ( $1, $2 );
272 if ( $valid_pve_network_keys ->{ $subkey }) {
273 $network ->{ $subkey } = $value ;
275 die "unable to parse config line: $line\n " ;
279 if ( $line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/ ) {
280 my ( $name, $value ) = ( $1, $2 );
281 $data ->{ $name } = $value ;
284 if ( $line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/ ) {
285 my ( $name, $value ) = ( $1, $2 );
287 die "multiple definitions for $name\n " if defined ( $data ->{ $name });
289 $data ->{ $name } = parse_lxc_option
( $name, $value );
293 die "unable to parse config line: $line\n " ;
296 & $push_network ( $network );
298 foreach my $net (@{ $network_list }) {
299 $net ->{ 'veth.pair' } = & $find_next_hostif_name () if ! $net ->{ 'veth.pair' };
300 $net ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $net ->{ hwaddr
};
301 die "unsupported network type ' $net ->{type}' \n " if $net ->{ type
} ne 'veth' ;
303 if ( $net ->{ 'veth.pair' } =~ m/^veth\d+.(\d+)$/ ) {
304 $data ->{ "net $1 " } = $net ;
312 my $vmlist = PVE
:: Cluster
:: get_vmlist
();
314 return $res if ! $vmlist || ! $vmlist ->{ ids
};
315 my $ids = $vmlist ->{ ids
};
317 foreach my $vmid ( keys %$ids ) {
318 next if ! $vmid ; # skip CT0
319 my $d = $ids ->{ $vmid };
320 next if ! $d ->{ node
} || $d ->{ node
} ne $nodename ;
321 next if ! $d ->{ type
} || $d ->{ type
} ne 'lxc' ;
322 $res ->{ $vmid }->{ type
} = 'lxc' ;
327 sub cfs_config_path
{
328 my ( $vmid, $node ) = @_ ;
330 $node = $nodename if ! $node ;
331 return "nodes/ $node/lxc/$vmid/config " ;
335 my ( $vmid, $node ) = @_ ;
337 my $cfspath = cfs_config_path
( $vmid, $node );
338 return "/etc/pve/ $cfspath " ;
344 my $cfspath = cfs_config_path
( $vmid );
346 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath );
347 die "container $vmid does not exists \n " if ! defined ( $conf );
353 my ( $vmid, $conf ) = @_ ;
355 my $cfspath = cfs_config_path
( $vmid );
357 PVE
:: Cluster
:: cfs_write_file
( $cfspath, $conf );
361 sub write_temp_config
{
362 my ( $vmid, $conf ) = @_ ;
365 my $filename = "/tmp/temp-lxc-conf- $vmid - $$ - $tempcounter .conf" ;
367 my $raw = write_lxc_config
( $filename, $conf );
369 PVE
:: Tools
:: file_set_contents
( $filename, $raw );
375 my ( $vmid, $timeout, $code, @param ) = @_ ;
377 my $lockdir = "/run/lock/lxc" ;
378 my $lockfile = " $lockdir/pve -config-{ $vmid }.lock" ;
380 File
:: Path
:: make_path
( $lockdir );
382 my $res = PVE
:: Tools
:: lock_file
( $lockfile, $timeout, $code, @param );
393 description
=> "Specifies whether a VM will be started during system bootup." ,
396 startup
=> get_standard_option
( 'pve-startup-order' ),
400 description
=> "The number of CPUs for this container (0 is unlimited)." ,
408 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." ,
416 description
=> "Amount of RAM for the VM in MB." ,
423 description
=> "Amount of SWAP for the VM in MB." ,
430 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits." ,
436 description
=> "Set a host name for the container." ,
443 description
=> "Container description. Only used on the configuration web interface." ,
448 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver." ,
453 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." ,
457 my $MAX_LXC_NETWORKS = 10 ;
458 for ( my $i = 0 ; $i < $MAX_LXC_NETWORKS ; $i++ ) {
459 $confdesc ->{ "net $i " } = {
461 type
=> 'string' , format
=> 'pve-lxc-network' ,
462 description
=> "Specifies network interfaces for the container." ,
469 return defined ( $confdesc ->{ $name });
472 # add JSON properties for create and set function
473 sub json_config_properties
{
476 foreach my $opt ( keys %$confdesc ) {
477 $prop ->{ $opt } = $confdesc ->{ $opt };
483 # container status helpers
485 sub list_active_containers
{
487 my $filename = "/proc/net/unix" ;
489 # similar test is used by lcxcontainers.c: list_active_containers
492 my $fh = IO
:: File-
> new ( $filename, "r" );
495 while ( defined ( my $line = < $fh >)) {
496 if ( $line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/ ) {
498 if ( $path =~ m!^@/etc/pve/lxc/(\d+)/command$! ) {
509 # warning: this is slow
513 my $active_hash = list_active_containers
();
515 return 1 if defined ( $active_hash ->{ $vmid });
523 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
525 my $active_hash = list_active_containers
();
527 foreach my $vmid ( keys %$list ) {
528 my $d = $list ->{ $vmid };
529 $d ->{ status
} = $active_hash ->{ $vmid } ?
'running' : 'stopped' ;
531 my $cfspath = cfs_config_path
( $vmid );
532 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath ) || {};
534 $d ->{ name
} = $conf ->{ 'lxc.utsname' } || "CT $vmid " ;
535 $d ->{ name
} =~ s/[\s]//g ;
539 my $cfs_period_us = $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
540 my $cfs_quota_us = $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
542 if ( $cfs_period_us && $cfs_quota_us ) {
543 $d ->{ cpus
} = int ( $cfs_quota_us/$cfs_period_us );
548 if ( my $private = $conf ->{ 'lxc.rootfs' }) {
549 my $res = PVE
:: Tools
:: df
( $private, 2 );
550 $d ->{ disk
} = $res ->{ used
};
551 $d ->{ maxdisk
} = $res ->{ total
};
556 $d ->{ maxmem
} = ( $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }|| 0 ) +
557 ( $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }|| 0 );
569 foreach my $vmid ( keys %$list ) {
570 my $d = $list ->{ $vmid };
571 next if $d ->{ status
} ne 'running' ;
573 $d ->{ uptime
} = 100 ; # fixme:
575 $d ->{ mem
} = read_cgroup_value
( 'memory' , $vmid, 'memory.usage_in_bytes' );
576 $d ->{ swap
} = read_cgroup_value
( 'memory' , $vmid, 'memory.memsw.usage_in_bytes' ) - $d ->{ mem
};
583 sub print_lxc_network
{
586 die "no network bridge defined \n " if ! $net ->{ bridge
};
588 my $res = "bridge= $net ->{bridge}" ;
590 foreach my $k ( qw(hwaddr mtu name ip gw ip6 gw6 firewall tag) ) {
591 next if ! defined ( $net ->{ $k });
592 $res .= ", $k = $net ->{ $k }" ;
598 sub parse_lxc_network
{
603 return $res if ! $data ;
605 foreach my $pv ( split ( /,/ , $data )) {
606 if ( $pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/ ) {
613 $res ->{ type
} = 'veth' ;
614 $res ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $res ->{ mac
};
619 sub read_cgroup_value
{
620 my ( $group, $vmid, $name, $full ) = @_ ;
622 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
624 return PVE
:: Tools
:: file_get_contents
( $path ) if $full ;
626 return PVE
:: Tools
:: file_read_firstline
( $path );
629 sub find_lxc_console_pids
{
633 PVE
:: Tools
:: dir_glob_foreach
( '/proc' , '\d+' , sub {
636 my $cmdline = PVE
:: Tools
:: file_read_firstline
( "/proc/ $pid/cmdline " );
639 my @args = split ( /\0/ , $cmdline );
641 # serach for lxc-console -n <vmid>
642 return if scalar ( @args ) != 3 ;
643 return if $args [ 1 ] ne '-n' ;
644 return if $args [ 2 ] !~ m/^\d+$/ ;
645 return if $args [ 0 ] !~ m
|^( /usr/ bin
/) ?lxc-console
$|;
649 push @{ $res ->{ $vmid }}, $pid ;
655 my $ipv4_reverse_mask = [
691 # Note: we cannot use Net:IP, because that only allows strict
693 sub parse_ipv4_cidr
{
694 my ( $cidr, $noerr ) = @_ ;
696 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
697 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
700 return undef if $noerr ;
702 die "unable to parse ipv4 address/mask \n " ;
705 sub lxc_conf_to_pve
{
706 my ( $vmid, $lxc_conf ) = @_ ;
708 my $properties = json_config_properties
();
710 my $conf = { digest
=> $lxc_conf ->{ digest
} };
712 foreach my $k ( keys %$properties ) {
714 if ( $k eq 'description' ) {
715 if ( my $raw = $lxc_conf ->{ 'pve.comment' }) {
716 $conf ->{ $k } = PVE
:: Tools
:: decode_text
( $raw );
718 } elsif ( $k eq 'onboot' ) {
719 $conf ->{ $k } = $lxc_conf ->{ 'pve.onboot' } if $lxc_conf ->{ 'pve.onboot' };
720 } elsif ( $k eq 'startup' ) {
721 $conf ->{ $k } = $lxc_conf ->{ 'pve.startup' } if $lxc_conf ->{ 'pve.startup' };
722 } elsif ( $k eq 'hostname' ) {
723 $conf ->{ $k } = $lxc_conf ->{ 'lxc.utsname' } if $lxc_conf ->{ 'lxc.utsname' };
724 } elsif ( $k eq 'nameserver' ) {
725 $conf ->{ $k } = $lxc_conf ->{ 'pve.nameserver' } if $lxc_conf ->{ 'pve.nameserver' };
726 } elsif ( $k eq 'searchdomain' ) {
727 $conf ->{ $k } = $lxc_conf ->{ 'pve.searchdomain' } if $lxc_conf ->{ 'pve.searchdomain' };
728 } elsif ( $k eq 'memory' ) {
729 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }) {
730 $conf ->{ $k } = int ( $value / ( 1024 * 1024 ));
732 } elsif ( $k eq 'swap' ) {
733 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }) {
734 my $mem = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } || 0 ;
735 $conf ->{ $k } = int (( $value - $mem ) / ( 1024 * 1024 ));
737 } elsif ( $k eq 'cpus' ) {
738 my $cfs_period_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
739 my $cfs_quota_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
741 if ( $cfs_period_us && $cfs_quota_us ) {
742 $conf ->{ $k } = int ( $cfs_quota_us/$cfs_period_us );
746 } elsif ( $k eq 'cpuunits' ) {
747 $conf ->{ $k } = $lxc_conf ->{ 'lxc.cgroup.cpu.shares' } || 1024 ;
748 } elsif ( $k =~ m/^net\d$/ ) {
749 my $net = $lxc_conf ->{ $k };
751 $conf ->{ $k } = print_lxc_network
( $net );
758 sub update_lxc_config
{
759 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
762 die "unable to modify config while container is running \n " if $running ;
764 if ( defined ( $delete )) {
765 foreach my $opt ( @$delete ) {
766 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
767 die "unable to delete required option ' $opt ' \n " ;
768 } elsif ( $opt eq 'swap' ) {
769 delete $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' };
770 } elsif ( $opt eq 'description' ) {
771 delete $conf ->{ 'pve.comment' };
772 } elsif ( $opt eq 'onboot' ) {
773 delete $conf ->{ 'pve.onboot' };
774 } elsif ( $opt eq 'startup' ) {
775 delete $conf ->{ 'pve.startup' };
776 } elsif ( $opt eq 'nameserver' ) {
777 delete $conf ->{ 'pve.nameserver' };
778 } elsif ( $opt eq 'searchdomain' ) {
779 delete $conf ->{ 'pve.searchdomain' };
780 } elsif ( $opt =~ m/^net\d$/ ) {
781 delete $conf ->{ $opt };
788 foreach my $opt ( keys %$param ) {
789 my $value = $param ->{ $opt };
790 if ( $opt eq 'hostname' ) {
791 $conf ->{ 'lxc.utsname' } = $value ;
792 } elsif ( $opt eq 'onboot' ) {
793 $conf ->{ 'pve.onboot' } = $value ?
1 : 0 ;
794 } elsif ( $opt eq 'startup' ) {
795 $conf ->{ 'pve.startup' } = $value ;
796 } elsif ( $opt eq 'nameserver' ) {
797 $conf ->{ 'pve.nameserver' } = $value ;
798 } elsif ( $opt eq 'searchdomain' ) {
799 $conf ->{ 'pve.searchdomain' } = $value ;
800 } elsif ( $opt eq 'memory' ) {
801 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
802 } elsif ( $opt eq 'swap' ) {
803 my $mem = $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' };
804 $mem = $param ->{ memory
}* 1024 * 1024 if $param ->{ memory
};
805 $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' } = $mem + $value*1024*1024 ;
806 } elsif ( $opt eq 'cpus' ) {
808 my $cfs_period_us = 100000 ;
809 $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' } = $cfs_period_us ;
810 $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' } = $cfs_period_us*$value ;
812 delete $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
813 delete $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
815 } elsif ( $opt eq 'cpuunits' ) {
816 $conf ->{ 'lxc.cgroup.cpu.shares' } = $value ;
817 } elsif ( $opt eq 'description' ) {
818 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
819 } elsif ( $opt =~ m/^net(\d+)$/ ) {
821 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
822 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
823 $conf ->{ $opt } = $net ;