]>
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.usage_in_bytes' => \
& parse_lxc_size
,
60 'lxc.mount.entry' => 1 ,
61 'lxc.mount.auto' => 1 ,
66 'lxc.haltsignal' => 1 ,
67 'lxc.rebootsignal' => 1 ,
68 'lxc.stopsignal' => 1 ,
71 'lxc.console.logfile' => 1 ,
77 'lxc.aa_profile' => 1 ,
78 'lxc.aa_allow_incomplete' => 1 ,
79 'lxc.se_context' => 1 ,
82 'lxc.environment' => 1 ,
86 'lxc.start.auto' => 1 ,
87 'lxc.start.delay' => 1 ,
88 'lxc.start.order' => 1 ,
92 'lxc.hook.pre-start' => 1 ,
93 'lxc.hook.pre-mount' => 1 ,
94 'lxc.hook.mount' => 1 ,
95 'lxc.hook.autodev' => 1 ,
96 'lxc.hook.start' => 1 ,
97 'lxc.hook.post-stop' => 1 ,
98 'lxc.hook.clone' => 1 ,
104 my $valid_lxc_network_keys = {
108 name
=> 1 , # ifname inside container
109 'veth.pair' => 1 , # ifname at host (eth${vmid}.X)
113 my $valid_pve_network_keys = {
120 my $lxc_array_configs = {
126 sub write_lxc_config
{
127 my ( $filename, $data ) = @_ ;
131 return $raw if ! $data ;
133 my $done_hash = { digest
=> 1 };
135 foreach my $k ( sort keys %$data ) {
136 next if $k !~ m/^lxc\./ ;
137 $done_hash ->{ $k } = 1 ;
138 $raw .= " $k = $data ->{ $k } \n " ;
141 foreach my $k ( sort keys %$data ) {
142 next if $k !~ m/^pve\./ ;
143 $done_hash ->{ $k } = 1 ;
144 $raw .= " $k = $data ->{ $k } \n " ;
147 foreach my $k ( sort keys %$data ) {
148 next if $k !~ m/^net\d+$/ ;
149 $done_hash ->{ $k } = 1 ;
150 my $net = $data ->{ $k };
151 $raw .= "lxc.network.type = $net ->{type} \n " ;
152 foreach my $subkey ( sort keys %$net ) {
153 next if $subkey eq 'type' ;
154 if ( $valid_lxc_network_keys ->{ $subkey }) {
155 $raw .= "lxc.network. $subkey = $net ->{ $subkey } \n " ;
156 } elsif ( $valid_pve_network_keys ->{ $subkey }) {
157 $raw .= "pve.network. $subkey = $net ->{ $subkey } \n " ;
159 die "found invalid network key ' $subkey '" ;
164 foreach my $k ( sort keys %$data ) {
165 next if $done_hash ->{ $k };
166 die "found un-written value in config - implement this!" ;
172 sub parse_lxc_option
{
173 my ( $name, $value ) = @_ ;
175 my $parser = $valid_lxc_keys ->{ $name };
177 die "inavlid key ' $name ' \n " if ! defined ( $parser );
179 if ( $parser eq '1' ) {
181 } elsif ( ref ( $parser )) {
182 my $res = & $parser ( $name, $value );
183 return $res if defined ( $res );
186 return $value if $value =~ m/^$parser$/ ;
189 die "unable to parse value ' $value ' for option ' $name ' \n " ;
192 sub parse_lxc_config
{
193 my ( $filename, $raw ) = @_ ;
195 return undef if ! defined ( $raw );
198 digest
=> Digest
:: SHA
:: sha1_hex
( $raw ),
201 $filename =~ m
| /lxc/ ( \d
+)/ config
$|
202 || die "got strange filename ' $filename '" ;
206 my $network_counter = 0 ;
207 my $network_list = [];
208 my $host_ifnames = {};
210 my $find_next_hostif_name = sub {
211 for ( my $i = 0 ; $i < 10 ; $i++ ) {
212 my $name = "veth${vmid}. $i " ;
213 if (! $host_ifnames ->{ $name }) {
214 $host_ifnames ->{ $name } = 1 ;
219 die "unable to find free host_ifname" ; # should not happen
222 my $push_network = sub {
225 push @{ $network_list }, $netconf ;
227 if ( my $netname = $netconf ->{ 'veth.pair' }) {
228 if ( $netname =~ m/^veth(\d+).(\d)$/ ) {
229 die "wrong vmid for network interface pair \n " if $1 != $vmid ;
230 my $host_ifnames ->{ $netname } = 1 ;
232 die "wrong network interface pair \n " ;
239 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
242 next if $line =~ m/^\#/ ;
243 next if $line =~ m/^\s*$/ ;
245 if ( $line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
246 my ( $subkey, $value ) = ( $1, $2 );
247 if ( $subkey eq 'type' ) {
248 & $push_network ( $network );
249 $network = { type
=> $value };
250 } elsif ( $valid_lxc_network_keys ->{ $subkey }) {
251 $network ->{ $subkey } = $value ;
253 die "unable to parse config line: $line\n " ;
257 if ( $line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
258 my ( $subkey, $value ) = ( $1, $2 );
259 if ( $valid_pve_network_keys ->{ $subkey }) {
260 $network ->{ $subkey } = $value ;
262 die "unable to parse config line: $line\n " ;
266 if ( $line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/ ) {
267 my ( $name, $value ) = ( $1, $2 );
268 $data ->{ $name } = $value ;
271 if ( $line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/ ) {
272 my ( $name, $value ) = ( $1, $2 );
274 die "multiple definitions for $name\n " if defined ( $data ->{ $name });
276 $data ->{ $name } = parse_lxc_option
( $name, $value );
280 die "unable to parse config line: $line\n " ;
283 & $push_network ( $network );
285 foreach my $net (@{ $network_list }) {
286 $net ->{ 'veth.pair' } = & $find_next_hostif_name () if ! $net ->{ 'veth.pair' };
287 $net ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $net ->{ hwaddr
};
288 die "unsupported network type ' $net ->{type}' \n " if $net ->{ type
} ne 'veth' ;
290 if ( $net ->{ 'veth.pair' } =~ m/^veth\d+.(\d+)$/ ) {
291 $data ->{ "net $1 " } = $net ;
299 my $vmlist = PVE
:: Cluster
:: get_vmlist
();
301 return $res if ! $vmlist || ! $vmlist ->{ ids
};
302 my $ids = $vmlist ->{ ids
};
304 foreach my $vmid ( keys %$ids ) {
305 next if ! $vmid ; # skip CT0
306 my $d = $ids ->{ $vmid };
307 next if ! $d ->{ node
} || $d ->{ node
} ne $nodename ;
308 next if ! $d ->{ type
} || $d ->{ type
} ne 'lxc' ;
309 $res ->{ $vmid }->{ type
} = 'lxc' ;
314 sub cfs_config_path
{
315 my ( $vmid, $node ) = @_ ;
317 $node = $nodename if ! $node ;
318 return "nodes/ $node/lxc/$vmid/config " ;
322 my ( $vmid, $node ) = @_ ;
324 my $cfspath = cfs_config_path
( $vmid, $node );
325 return "/etc/pve/ $cfspath " ;
331 my $cfspath = cfs_config_path
( $vmid );
333 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath );
334 die "container $vmid does not exists \n " if ! defined ( $conf );
340 my ( $vmid, $conf ) = @_ ;
342 my $cfspath = cfs_config_path
( $vmid );
344 PVE
:: Cluster
:: cfs_write_file
( $cfspath, $conf );
348 sub write_temp_config
{
349 my ( $vmid, $conf ) = @_ ;
352 my $filename = "/tmp/temp-lxc-conf- $vmid - $$ - $tempcounter .conf" ;
354 my $raw = write_lxc_config
( $filename, $conf );
356 PVE
:: Tools
:: file_set_contents
( $filename, $raw );
362 my ( $vmid, $timeout, $code, @param ) = @_ ;
364 my $lockdir = "/run/lock/lxc" ;
365 my $lockfile = " $lockdir/pve -config-{ $vmid }.lock" ;
367 File
:: Path
:: make_path
( $lockdir );
369 my $res = PVE
:: Tools
:: lock_file
( $lockfile, $timeout, $code, @param );
380 description
=> "Specifies whether a VM will be started during system bootup." ,
386 description
=> "The number of CPUs for this container." ,
393 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." ,
401 description
=> "Amount of RAM for the VM in MB." ,
408 description
=> "Amount of SWAP for the VM in MB." ,
415 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits." ,
421 description
=> "Set a host name for the container." ,
428 description
=> "Container description. Only used on the configuration web interface." ,
433 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver." ,
438 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." ,
442 my $MAX_LXC_NETWORKS = 10 ;
443 for ( my $i = 0 ; $i < $MAX_LXC_NETWORKS ; $i++ ) {
444 $confdesc ->{ "net $i " } = {
446 type
=> 'string' , format
=> 'pve-lxc-network' ,
447 description
=> "Specifies network interfaces for the container." ,
454 return defined ( $confdesc ->{ $name });
457 # add JSON properties for create and set function
458 sub json_config_properties
{
461 foreach my $opt ( keys %$confdesc ) {
462 $prop ->{ $opt } = $confdesc ->{ $opt };
468 # container status helpers
470 sub list_active_containers
{
472 my $filename = "/proc/net/unix" ;
474 # similar test is used by lcxcontainers.c: list_active_containers
477 my $fh = IO
:: File-
> new ( $filename, "r" );
480 while ( defined ( my $line = < $fh >)) {
481 if ( $line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/ ) {
483 if ( $path =~ m!^@/etc/pve/lxc/(\d+)/command$! ) {
494 # warning: this is slow
498 my $active_hash = list_active_containers
();
500 return 1 if defined ( $active_hash ->{ $vmid });
508 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
510 my $active_hash = list_active_containers
();
512 foreach my $vmid ( keys %$list ) {
513 my $d = $list ->{ $vmid };
514 $d ->{ status
} = $active_hash ->{ $vmid } ?
'running' : 'stopped' ;
516 my $cfspath = cfs_config_path
( $vmid );
517 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath ) || {};
519 $d ->{ name
} = $conf ->{ 'lxc.utsname' } || "CT $vmid " ;
520 $d ->{ name
} =~ s/[\s]//g ;
522 $d ->{ cpus
} = 1 ; # fixme:
526 if ( my $private = $conf ->{ 'lxc.rootfs' }) {
527 my $res = PVE
:: Tools
:: df
( $private, 2 );
528 $d ->{ disk
} = $res ->{ used
};
529 $d ->{ maxdisk
} = $res ->{ total
};
534 $d ->{ maxmem
} = ( $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }|| 0 ) +
535 ( $conf ->{ 'lxc.cgroup.memory.memsw.usage_in_bytes' }|| 0 );
547 foreach my $vmid ( keys %$list ) {
548 my $d = $list ->{ $vmid };
549 next if $d ->{ status
} ne 'running' ;
551 $d ->{ uptime
} = 100 ; # fixme:
553 $d ->{ mem
} = read_cgroup_value
( 'memory' , $vmid, 'memory.usage_in_bytes' );
554 $d ->{ swap
} = read_cgroup_value
( 'memory' , $vmid, 'memory.memsw.usage_in_bytes' ) - $d ->{ mem
};
561 sub print_lxc_network
{
564 die "no network link defined \n " if ! $net ->{ link };
566 my $res = "link= $net ->{link}" ;
568 foreach my $k ( qw(hwaddr mtu name ip gw ip6 gw6) ) {
569 next if ! defined ( $net ->{ $k });
570 $res .= ", $k = $net ->{ $k }" ;
576 sub parse_lxc_network
{
581 return $res if ! $data ;
583 foreach my $pv ( split ( /,/ , $data )) {
584 if ( $pv =~ m/^(link|hwaddr|mtu|name|ip|ip6|gw|gw6)=(\S+)$/ ) {
591 $res ->{ type
} = 'veth' ;
592 $res ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $res ->{ mac
};
597 sub read_cgroup_value
{
598 my ( $group, $vmid, $name, $full ) = @_ ;
600 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
602 return PVE
:: Tools
:: file_get_contents
( $path ) if $full ;
604 return PVE
:: Tools
:: file_read_firstline
( $path );
607 sub find_lxc_console_pids
{
611 PVE
:: Tools
:: dir_glob_foreach
( '/proc' , '\d+' , sub {
614 my $cmdline = PVE
:: Tools
:: file_read_firstline
( "/proc/ $pid/cmdline " );
617 my @args = split ( /\0/ , $cmdline );
619 # serach for lxc-console -n <vmid>
620 return if scalar ( @args ) != 3 ;
621 return if $args [ 1 ] ne '-n' ;
622 return if $args [ 2 ] !~ m/^\d+$/ ;
623 return if $args [ 0 ] !~ m
|^( /usr/ bin
/) ?lxc-console
$|;
627 push @{ $res ->{ $vmid }}, $pid ;
633 my $ipv4_reverse_mask = [
669 # Note: we cannot use Net:IP, because that only allows strict
671 sub parse_ipv4_cidr
{
672 my ( $cidr, $noerr ) = @_ ;
674 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
675 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
678 return undef if $noerr ;
680 die "unable to parse ipv4 address/mask \n " ;
683 sub update_lxc_config
{
684 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
687 die "unable to modify config while container is running \n " if $running ;
689 if ( defined ( $delete )) {
690 foreach my $opt ( @$delete ) {
691 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
692 die "unable to delete required option ' $opt ' \n " ;
693 } elsif ( $opt eq 'swap' ) {
694 delete $conf ->{ 'lxc.cgroup.memory.memsw.usage_in_bytes' };
695 } elsif ( $opt eq 'description' ) {
696 delete $conf ->{ 'pve.comment' };
697 } elsif ( $opt =~ m/^net\d$/ ) {
698 delete $conf ->{ $opt };
705 foreach my $opt ( keys %$param ) {
706 my $value = $param ->{ $opt };
707 if ( $opt eq 'hostname' ) {
708 $conf ->{ 'lxc.utsname' } = $value ;
709 } elsif ( $opt eq 'memory' ) {
710 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
711 } elsif ( $opt eq 'swap' ) {
712 $conf ->{ 'lxc.cgroup.memory.memsw.usage_in_bytes' } = $value*1024*1024 ;
713 } elsif ( $opt eq 'description' ) {
714 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
715 } elsif ( $opt =~ m/^net(\d+)$/ ) {
717 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
718 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
719 $conf ->{ $opt } = $net ;