]>
git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
9 use PVE
:: Cluster
qw(cfs_register_file cfs_read_file) ;
15 cfs_register_file
( '/lxc/' , \
& parse_lxc_config
, \
& write_lxc_config
);
17 PVE
:: JSONSchema
:: register_format
( 'pve-lxc-network' , \
& verify_lxc_network
);
18 sub verify_lxc_network
{
19 my ( $value, $noerr ) = @_ ;
21 return $value if parse_lxc_network
( $value );
23 return undef if $noerr ;
25 die "unable to parse network setting \n " ;
28 my $nodename = PVE
:: INotify
:: nodename
();
31 my ( $name, $value ) = @_ ;
33 if ( $value =~ m/^(\d+)(b|k|m|g)?$/i ) {
34 my ( $res, $unit ) = ( $1, lc ( $2 || 'b' ));
36 return $res if $unit eq 'b' ;
37 return $res*1024 if $unit eq 'k' ;
38 return $res*1024*1024 if $unit eq 'm' ;
39 return $res*1024*1024*1024 if $unit eq 'g' ;
45 my $valid_lxc_keys = {
46 'lxc.arch' => 'i386|x86|i686|x86_64|amd64' ,
54 'lxc.cgroup.memory.limit_in_bytes' => \
& parse_lxc_size
,
55 'lxc.cgroup.memory.memsw.usage_in_bytes' => \
& parse_lxc_size
,
59 'lxc.mount.entry' => 1 ,
60 'lxc.mount.auto' => 1 ,
65 'lxc.haltsignal' => 1 ,
66 'lxc.rebootsignal' => 1 ,
67 'lxc.stopsignal' => 1 ,
70 'lxc.console.logfile' => 1 ,
76 'lxc.aa_profile' => 1 ,
77 'lxc.aa_allow_incomplete' => 1 ,
78 'lxc.se_context' => 1 ,
81 'lxc.environment' => 1 ,
85 'lxc.start.auto' => 1 ,
86 'lxc.start.delay' => 1 ,
87 'lxc.start.order' => 1 ,
91 'lxc.hook.pre-start' => 1 ,
92 'lxc.hook.pre-mount' => 1 ,
93 'lxc.hook.mount' => 1 ,
94 'lxc.hook.autodev' => 1 ,
95 'lxc.hook.start' => 1 ,
96 'lxc.hook.post-stop' => 1 ,
97 'lxc.hook.clone' => 1 ,
103 my $valid_network_keys = {
108 name
=> 1 , # ifname inside container
109 'veth.pair' => 1 , # ifname at host (eth${vmid}.X)
117 my $lxc_array_configs = {
123 sub write_lxc_config
{
124 my ( $filename, $data ) = @_ ;
128 return $raw if ! $data ;
130 my $done_hash = { digest
=> 1 };
132 foreach my $k ( sort keys %$data ) {
133 next if $k !~ m/^lxc\./ ;
134 $done_hash ->{ $k } = 1 ;
135 $raw .= " $k = $data ->{ $k } \n " ;
138 foreach my $k ( sort keys %$data ) {
139 next if $k !~ m/^pve\./ ;
140 $done_hash ->{ $k } = 1 ;
141 $raw .= " $k = $data ->{ $k } \n " ;
144 foreach my $k ( sort keys %$data ) {
145 next if $k !~ m/^net\d+$/ ;
146 $done_hash ->{ $k } = 1 ;
147 my $net = $data ->{ $k };
148 $raw .= "lxc.network.type = $net ->{type} \n " ;
149 foreach my $subkey ( sort keys %$net ) {
150 next if $subkey eq 'type' ;
151 $raw .= "lxc.network. $subkey = $net ->{ $subkey } \n " ;
155 foreach my $k ( sort keys %$data ) {
156 next if $done_hash ->{ $k };
157 die "found un-written value in config - implement this!" ;
163 sub parse_lxc_option
{
164 my ( $name, $value ) = @_ ;
166 my $parser = $valid_lxc_keys ->{ $name };
168 die "inavlid key ' $name ' \n " if ! defined ( $parser );
170 if ( $parser eq '1' ) {
172 } elsif ( ref ( $parser )) {
173 my $res = & $parser ( $name, $value );
174 return $res if defined ( $res );
177 return $value if $value =~ m/^$parser$/ ;
180 die "unable to parse value ' $value ' for option ' $name ' \n " ;
183 sub parse_lxc_config
{
184 my ( $filename, $raw ) = @_ ;
186 return undef if ! defined ( $raw );
189 digest
=> Digest
:: SHA
:: sha1_hex
( $raw ),
192 $filename =~ m
| /lxc/ ( \d
+)/ config
$|
193 || die "got strange filename ' $filename '" ;
197 my $network_counter = 0 ;
198 my $network_list = [];
199 my $host_ifnames = {};
201 my $find_next_hostif_name = sub {
202 for ( my $i = 0 ; $i < 10 ; $i++ ) {
203 my $name = "veth${vmid}. $i " ;
204 if (! $host_ifnames ->{ $name }) {
205 $host_ifnames ->{ $name } = 1 ;
210 die "unable to find free host_ifname" ; # should not happen
213 my $push_network = sub {
216 push @{ $network_list }, $netconf ;
218 if ( my $netname = $netconf ->{ 'veth.pair' }) {
219 if ( $netname =~ m/^veth(\d+).(\d)$/ ) {
220 die "wrong vmid for network interface pair \n " if $1 != $vmid ;
221 my $host_ifnames ->{ $netname } = 1 ;
223 die "wrong network interface pair \n " ;
230 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
233 next if $line =~ m/^\#/ ;
234 next if $line =~ m/^\s*$/ ;
236 if ( $line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
237 my ( $subkey, $value ) = ( $1, $2 );
238 if ( $subkey eq 'type' ) {
239 & $push_network ( $network );
240 $network = { type
=> $value };
241 } elsif ( $valid_network_keys ->{ $subkey }) {
242 $network ->{ $subkey } = $value ;
244 die "unable to parse config line: $line\n " ;
249 if ( $line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/ ) {
250 my ( $name, $value ) = ( $1, $2 );
251 $data ->{ $name } = $value ;
254 if ( $line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/ ) {
255 my ( $name, $value ) = ( $1, $2 );
257 die "multiple definitions for $name\n " if defined ( $data ->{ $name });
259 $data ->{ $name } = parse_lxc_option
( $name, $value );
263 die "unable to parse config line: $line\n " ;
266 & $push_network ( $network );
268 foreach my $net (@{ $network_list }) {
269 $net ->{ 'veth.pair' } = & $find_next_hostif_name () if ! $net ->{ 'veth.pair' };
270 $net ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $net ->{ hwaddr
};
271 die "unsupported network type ' $net ->{type}' \n " if $net ->{ type
} ne 'veth' ;
273 if ( $net ->{ 'veth.pair' } =~ m/^veth\d+.(\d+)$/ ) {
274 $data ->{ "net $1 " } = $net ;
282 my $vmlist = PVE
:: Cluster
:: get_vmlist
();
284 return $res if ! $vmlist || ! $vmlist ->{ ids
};
285 my $ids = $vmlist ->{ ids
};
287 foreach my $vmid ( keys %$ids ) {
288 next if ! $vmid ; # skip CT0
289 my $d = $ids ->{ $vmid };
290 next if ! $d ->{ node
} || $d ->{ node
} ne $nodename ;
291 next if ! $d ->{ type
} || $d ->{ type
} ne 'lxc' ;
292 $res ->{ $vmid }->{ type
} = 'lxc' ;
297 sub cfs_config_path
{
298 my ( $vmid, $node ) = @_ ;
300 $node = $nodename if ! $node ;
301 return "nodes/ $node/lxc/$vmid/config " ;
305 my ( $vmid, $node ) = @_ ;
307 my $cfspath = cfs_config_path
( $vmid, $node );
308 return "/etc/pve/ $cfspath " ;
314 my $cfspath = cfs_config_path
( $vmid );
316 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath );
317 die "container $vmid does not exists \n " if ! defined ( $conf );
323 my ( $vmid, $conf ) = @_ ;
325 my $cfspath = cfs_config_path
( $vmid );
327 PVE
:: Cluster
:: cfs_write_file
( $cfspath, $conf );
331 sub write_temp_config
{
332 my ( $vmid, $conf ) = @_ ;
335 my $filename = "/tmp/temp-lxc-conf- $vmid - $$ - $tempcounter .conf" ;
337 my $raw = write_lxc_config
( $filename, $conf );
339 PVE
:: Tools
:: file_set_contents
( $filename, $raw );
345 my ( $vmid, $timeout, $code, @param ) = @_ ;
347 my $lockdir = "/run/lock/lxc" ;
348 my $lockfile = " $lockdir/pve -config-{ $vmid }.lock" ;
350 File
:: Path
:: make_path
( $lockdir );
352 my $res = PVE
:: Tools
:: lock_file
( $lockfile, $timeout, $code, @param );
363 description
=> "Specifies whether a VM will be started during system bootup." ,
369 description
=> "The number of CPUs for this container." ,
376 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." ,
384 description
=> "Amount of RAM for the VM in MB." ,
391 description
=> "Amount of SWAP for the VM in MB." ,
398 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits." ,
404 description
=> "Set a host name for the container." ,
411 description
=> "Container description. Only used on the configuration web interface." ,
416 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver." ,
421 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." ,
425 my $MAX_LXC_NETWORKS = 10 ;
426 for ( my $i = 0 ; $i < $MAX_LXC_NETWORKS ; $i++ ) {
427 $confdesc ->{ "net $i " } = {
429 type
=> 'string' , format
=> 'pve-lxc-network' ,
430 description
=> "Specifies network interfaces for the container." ,
437 return defined ( $confdesc ->{ $name });
440 # add JSON properties for create and set function
441 sub json_config_properties
{
444 foreach my $opt ( keys %$confdesc ) {
445 $prop ->{ $opt } = $confdesc ->{ $opt };
451 # container status helpers
453 sub list_active_containers
{
455 my $filename = "/proc/net/unix" ;
457 # similar test is used by lcxcontainers.c: list_active_containers
460 my $fh = IO
:: File-
> new ( $filename, "r" );
463 while ( defined ( my $line = < $fh >)) {
464 if ( $line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/ ) {
466 if ( $path =~ m!^@/etc/pve/lxc/(\d+)/command$! ) {
477 # warning: this is slow
481 my $active_hash = list_active_containers
();
483 return 1 if defined ( $active_hash ->{ $vmid });
491 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
493 my $active_hash = list_active_containers
();
495 foreach my $vmid ( keys %$list ) {
496 my $d = $list ->{ $vmid };
497 $d ->{ status
} = $active_hash ->{ $vmid } ?
'running' : 'stopped' ;
499 my $cfspath = cfs_config_path
( $vmid );
500 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath ) || {};
502 $d ->{ name
} = $conf ->{ 'lxc.utsname' } || "CT $vmid " ;
503 $d ->{ name
} =~ s/[\s]//g ;
505 $d ->{ cpus
} = 1 ; # fixme:
509 if ( my $private = $conf ->{ 'lxc.rootfs' }) {
510 my $res = PVE
:: Tools
:: df
( $private, 2 );
511 $d ->{ disk
} = $res ->{ used
};
512 $d ->{ maxdisk
} = $res ->{ total
};
517 $d ->{ maxmem
} = ( $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }|| 0 ) +
518 ( $conf ->{ 'lxc.cgroup.memory.memsw.usage_in_bytes' }|| 0 );
530 foreach my $vmid ( keys %$list ) {
531 my $d = $list ->{ $vmid };
532 next if $d ->{ status
} ne 'running' ;
534 $d ->{ uptime
} = 100 ; # fixme:
536 $d ->{ mem
} = read_cgroup_value
( 'memory' , $vmid, 'memory.usage_in_bytes' );
537 $d ->{ swap
} = read_cgroup_value
( 'memory' , $vmid, 'memory.memsw.usage_in_bytes' ) - $d ->{ mem
};
544 sub print_lxc_network
{
547 die "no network link defined \n " if ! $net ->{ link };
549 my $res = "link= $net ->{link}" ;
551 foreach my $k ( qw(hwaddr mtu name ipv4 ipv4.gateway ipv6 ipv6.gateway) ) {
552 next if ! defined ( $net ->{ $k });
553 $res .= ", $k = $net ->{ $k }" ;
559 sub parse_lxc_network
{
564 return $res if ! $data ;
566 foreach my $pv ( split ( /,/ , $data )) {
567 if ( $pv =~ m/^(link|hwaddr|mtu|name|ipv4|ipv6|ipv4\.gateway|ipv6\.gateway)=(\S+)$/ ) {
574 $res ->{ type
} = 'veth' ;
575 $res ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $res ->{ mac
};
580 sub read_cgroup_value
{
581 my ( $group, $vmid, $name, $full ) = @_ ;
583 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
585 return PVE
:: Tools
:: file_get_contents
( $path ) if $full ;
587 return PVE
:: Tools
:: file_read_firstline
( $path );
590 sub find_lxc_console_pids
{
594 PVE
:: Tools
:: dir_glob_foreach
( '/proc' , '\d+' , sub {
597 my $cmdline = PVE
:: Tools
:: file_read_firstline
( "/proc/ $pid/cmdline " );
600 my @args = split ( /\0/ , $cmdline );
602 # serach for lxc-console -n <vmid>
603 return if scalar ( @args ) != 3 ;
604 return if $args [ 1 ] ne '-n' ;
605 return if $args [ 2 ] !~ m/^\d+$/ ;
606 return if $args [ 0 ] !~ m
|^( /usr/ bin
/) ?lxc-console
$|;
610 push @{ $res ->{ $vmid }}, $pid ;