]>
git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
10 use PVE
:: Cluster
qw(cfs_register_file cfs_read_file) ;
14 use PVE
:: JSONSchema
qw(get_standard_option) ;
15 use PVE
:: Tools
qw( $IPV6RE $IPV4RE ) ;
20 cfs_register_file
( '/lxc/' , \
& parse_lxc_config
, \
& write_lxc_config
);
22 PVE
:: JSONSchema
:: register_format
( 'pve-lxc-network' , \
& verify_lxc_network
);
23 sub verify_lxc_network
{
24 my ( $value, $noerr ) = @_ ;
26 return $value if parse_lxc_network
( $value );
28 return undef if $noerr ;
30 die "unable to parse network setting \n " ;
33 my $nodename = PVE
:: INotify
:: nodename
();
36 my ( $name, $value ) = @_ ;
38 if ( $value =~ m/^(\d+)(b|k|m|g)?$/i ) {
39 my ( $res, $unit ) = ( $1, lc ( $2 || 'b' ));
41 return $res if $unit eq 'b' ;
42 return $res*1024 if $unit eq 'k' ;
43 return $res*1024*1024 if $unit eq 'm' ;
44 return $res*1024*1024*1024 if $unit eq 'g' ;
50 my $valid_lxc_keys = {
51 'lxc.arch' => 'i386|x86|i686|x86_64|amd64' ,
59 'lxc.cgroup.memory.limit_in_bytes' => \
& parse_lxc_size
,
60 'lxc.cgroup.memory.memsw.limit_in_bytes' => \
& parse_lxc_size
,
61 'lxc.cgroup.cpu.cfs_period_us' => '\d+' ,
62 'lxc.cgroup.cpu.cfs_quota_us' => '\d+' ,
63 'lxc.cgroup.cpu.shares' => '\d+' ,
67 'lxc.mount.entry' => 1 ,
68 'lxc.mount.auto' => 1 ,
73 'lxc.haltsignal' => 1 ,
74 'lxc.rebootsignal' => 1 ,
75 'lxc.stopsignal' => 1 ,
78 'lxc.console.logfile' => 1 ,
84 'lxc.aa_profile' => 1 ,
85 'lxc.aa_allow_incomplete' => 1 ,
86 'lxc.se_context' => 1 ,
89 'lxc.environment' => 1 ,
90 'lxc.cgroup.devices.deny' => 1 ,
93 'lxc.start.auto' => 1 ,
94 'lxc.start.delay' => 1 ,
95 'lxc.start.order' => 1 ,
99 'lxc.hook.pre-start' => 1 ,
100 'lxc.hook.pre-mount' => 1 ,
101 'lxc.hook.mount' => 1 ,
102 'lxc.hook.autodev' => 1 ,
103 'lxc.hook.start' => 1 ,
104 'lxc.hook.post-stop' => 1 ,
105 'lxc.hook.clone' => 1 ,
108 'pve.nameserver' => sub {
109 my ( $name, $value ) = @_ ;
110 return verify_nameserver_list
( $value );
112 'pve.searchdomain' => sub {
113 my ( $name, $value ) = @_ ;
114 return verify_searchdomain_list
( $value );
116 'pve.onboot' => '(0|1)' ,
117 'pve.startup' => sub {
118 my ( $name, $value ) = @_ ;
119 return PVE
:: JSONSchema
:: pve_verify_startup_order
( $value );
122 'pve.disksize' => '\d+(\.\d+)?' ,
124 my ( $name, $value ) = @_ ;
125 PVE
:: Storage
:: parse_volume_id
( $value );
130 my $valid_lxc_network_keys = {
133 name
=> 1 , # ifname inside container
134 'veth.pair' => 1 , # ifname at host (eth${vmid}.X)
138 my $valid_pve_network_keys = {
148 my $lxc_array_configs = {
153 'lxc.cgroup.devices.deny' => 1 ,
156 sub write_lxc_config
{
157 my ( $filename, $data ) = @_ ;
161 return $raw if ! $data ;
163 my $done_hash = { digest
=> 1 };
165 my $dump_entry = sub {
167 my $value = $data ->{ $k };
168 return if ! defined ( $value );
169 return if $done_hash ->{ $k };
170 $done_hash ->{ $k } = 1 ;
172 die "got unexpected reference for ' $k '"
173 if ! $lxc_array_configs ->{ $k };
174 foreach my $v ( @$value ) {
178 $raw .= " $k = $value\n " ;
182 # Note: Order is important! Include defaults first, so that we
183 # can overwrite them later.
184 & $dump_entry ( 'lxc.include' );
186 foreach my $k ( sort keys %$data ) {
187 next if $k !~ m/^lxc\./ ;
191 foreach my $k ( sort keys %$data ) {
192 next if $k !~ m/^pve\./ ;
196 my $network_count = 0 ;
197 foreach my $k ( sort keys %$data ) {
198 next if $k !~ m/^net\d+$/ ;
199 $done_hash ->{ $k } = 1 ;
200 my $net = $data ->{ $k };
202 $raw .= "lxc.network.type = $net ->{type} \n " ;
203 foreach my $subkey ( sort keys %$net ) {
204 next if $subkey eq 'type' ;
205 if ( $valid_lxc_network_keys ->{ $subkey }) {
206 $raw .= "lxc.network. $subkey = $net ->{ $subkey } \n " ;
207 } elsif ( $valid_pve_network_keys ->{ $subkey }) {
208 $raw .= "pve.network. $subkey = $net ->{ $subkey } \n " ;
210 die "found invalid network key ' $subkey '" ;
215 if (! $network_count ) {
216 $raw .= "lxc.network.type = empty \n " ;
219 foreach my $k ( sort keys %$data ) {
220 next if $done_hash ->{ $k };
221 die "found un-written value in config - implement this!" ;
227 sub parse_lxc_option
{
228 my ( $name, $value ) = @_ ;
230 my $parser = $valid_lxc_keys ->{ $name };
232 die "invalid key ' $name ' \n " if ! defined ( $parser );
234 if ( $parser eq '1' ) {
236 } elsif ( ref ( $parser )) {
237 my $res = & $parser ( $name, $value );
238 return $res if defined ( $res );
241 return $value if $value =~ m/^$parser$/ ;
244 die "unable to parse value ' $value ' for option ' $name ' \n " ;
247 sub parse_lxc_config
{
248 my ( $filename, $raw ) = @_ ;
250 return undef if ! defined ( $raw );
253 digest
=> Digest
:: SHA
:: sha1_hex
( $raw ),
256 $filename =~ m
| /lxc/ ( \d
+)/ config
$|
257 || die "got strange filename ' $filename '" ;
261 my $network_counter = 0 ;
262 my $network_list = [];
263 my $host_ifnames = {};
265 my $find_next_hostif_name = sub {
266 for ( my $i = 0 ; $i < 10 ; $i++ ) {
267 my $name = "veth${vmid}. $i " ;
268 if (! $host_ifnames ->{ $name }) {
269 $host_ifnames ->{ $name } = 1 ;
274 die "unable to find free host_ifname" ; # should not happen
277 my $push_network = sub {
280 push @{ $network_list }, $netconf ;
282 if ( my $netname = $netconf ->{ 'veth.pair' }) {
283 if ( $netname =~ m/^veth(\d+).(\d)$/ ) {
284 die "wrong vmid for network interface pair \n " if $1 != $vmid ;
285 my $host_ifnames ->{ $netname } = 1 ;
287 die "wrong network interface pair \n " ;
294 while ( $raw && $raw =~ s/^(.*?)(\n|$)// ) {
297 next if $line =~ m/^\#/ ;
298 next if $line =~ m/^\s*$/ ;
300 if ( $line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
301 my ( $subkey, $value ) = ( $1, $2 );
302 if ( $subkey eq 'type' ) {
303 & $push_network ( $network );
304 $network = { type
=> $value };
305 } elsif ( $valid_lxc_network_keys ->{ $subkey }) {
306 $network ->{ $subkey } = $value ;
308 die "unable to parse config line: $line\n " ;
312 if ( $line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
313 my ( $subkey, $value ) = ( $1, $2 );
314 if ( $valid_pve_network_keys ->{ $subkey }) {
315 $network ->{ $subkey } = $value ;
317 die "unable to parse config line: $line\n " ;
321 if ( $line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/ ) {
322 my ( $name, $value ) = ( $1, $2 );
323 $data ->{ $name } = $value ;
326 if ( $line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/ ) {
327 my ( $name, $value ) = ( $1, $2 );
329 if ( $lxc_array_configs ->{ $name }) {
330 $data ->{ $name } = [] if ! defined ( $data ->{ $name });
331 push @{ $data ->{ $name }}, parse_lxc_option
( $name, $value );
333 die "multiple definitions for $name\n " if defined ( $data ->{ $name });
334 $data ->{ $name } = parse_lxc_option
( $name, $value );
340 die "unable to parse config line: $line\n " ;
343 & $push_network ( $network );
345 foreach my $net (@{ $network_list }) {
346 next if $net ->{ type
} eq 'empty' ; # skip
347 $net ->{ 'veth.pair' } = & $find_next_hostif_name () if ! $net ->{ 'veth.pair' };
348 $net ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $net ->{ hwaddr
};
349 die "unsupported network type ' $net ->{type}' \n " if $net ->{ type
} ne 'veth' ;
351 if ( $net ->{ 'veth.pair' } =~ m/^veth\d+.(\d+)$/ ) {
352 $data ->{ "net $1 " } = $net ;
360 my $vmlist = PVE
:: Cluster
:: get_vmlist
();
362 return $res if ! $vmlist || ! $vmlist ->{ ids
};
363 my $ids = $vmlist ->{ ids
};
365 foreach my $vmid ( keys %$ids ) {
366 next if ! $vmid ; # skip CT0
367 my $d = $ids ->{ $vmid };
368 next if ! $d ->{ node
} || $d ->{ node
} ne $nodename ;
369 next if ! $d ->{ type
} || $d ->{ type
} ne 'lxc' ;
370 $res ->{ $vmid }->{ type
} = 'lxc' ;
375 sub cfs_config_path
{
376 my ( $vmid, $node ) = @_ ;
378 $node = $nodename if ! $node ;
379 return "nodes/ $node/lxc/$vmid/config " ;
383 my ( $vmid, $node ) = @_ ;
385 my $cfspath = cfs_config_path
( $vmid, $node );
386 return "/etc/pve/ $cfspath " ;
392 my $cfspath = cfs_config_path
( $vmid );
394 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath );
395 die "container $vmid does not exists \n " if ! defined ( $conf );
401 my ( $vmid, $conf ) = @_ ;
403 my $dir = "/etc/pve/nodes/ $nodename/lxc " ;
407 mkdir ( $dir ) || die "unable to create container configuration directory - $!\n " ;
409 write_config
( $vmid, $conf );
415 my $dir = "/etc/pve/nodes/ $nodename/lxc/$vmid " ;
416 File
:: Path
:: rmtree
( $dir );
420 my ( $vmid, $conf ) = @_ ;
422 my $cfspath = cfs_config_path
( $vmid );
424 PVE
:: Cluster
:: cfs_write_file
( $cfspath, $conf );
428 sub write_temp_config
{
429 my ( $vmid, $conf ) = @_ ;
432 my $filename = "/tmp/temp-lxc-conf- $vmid - $$ - $tempcounter .conf" ;
434 my $raw = write_lxc_config
( $filename, $conf );
436 PVE
:: Tools
:: file_set_contents
( $filename, $raw );
441 # flock: we use one file handle per process, so lock file
442 # can be called multiple times and succeeds for the same process.
444 my $lock_handles = {};
445 my $lockdir = "/run/lock/lxc" ;
450 return " $lockdir/pve -config-{ $vmid }.lock" ;
454 my ( $vmid, $timeout ) = @_ ;
456 $timeout = 10 if ! $timeout ;
459 my $filename = lock_filename
( $vmid );
461 mkdir $lockdir if !- d
$lockdir ;
463 my $lock_func = sub {
464 if (! $lock_handles ->{ $$ }->{ $filename }) {
465 my $fh = new IO
:: File
( ">> $filename " ) ||
466 die "can't open file - $!\n " ;
467 $lock_handles ->{ $$ }->{ $filename } = { fh
=> $fh, refcount
=> 0 };
470 if (! flock ( $lock_handles ->{ $$ }->{ $filename }->{ fh
}, $mode | LOCK_NB
)) {
471 print STDERR
"trying to aquire lock..." ;
474 $success = flock ( $lock_handles ->{ $$ }->{ $filename }->{ fh
}, $mode );
475 # try again on EINTR (see bug #273)
476 if ( $success || ( $! != EINTR
)) {
481 print STDERR
" failed \n " ;
482 die "can't aquire lock - $!\n " ;
485 $lock_handles ->{ $$ }->{ $filename }->{ refcount
}++;
487 print STDERR
" OK \n " ;
491 eval { PVE
:: Tools
:: run_with_timeout
( $timeout, $lock_func ); };
494 die "can't lock file ' $filename ' - $err " ;
501 my $filename = lock_filename
( $vmid );
503 if ( my $fh = $lock_handles ->{ $$ }->{ $filename }->{ fh
}) {
504 my $refcount = -- $lock_handles ->{ $$ }->{ $filename }->{ refcount
};
505 if ( $refcount <= 0 ) {
506 $lock_handles ->{ $$ }->{ $filename } = undef ;
513 my ( $vmid, $timeout, $code, @param ) = @_ ;
517 lock_aquire
( $vmid, $timeout );
518 eval { $res = & $code ( @param ) };
531 description
=> "Specifies whether a VM will be started during system bootup." ,
534 startup
=> get_standard_option
( 'pve-startup-order' ),
538 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit." ,
546 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." ,
554 description
=> "Amount of RAM for the VM in MB." ,
561 description
=> "Amount of SWAP for the VM in MB." ,
568 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits." ,
574 description
=> "Set a host name for the container." ,
581 description
=> "Container description. Only used on the configuration web interface." ,
586 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver." ,
591 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." ,
595 my $MAX_LXC_NETWORKS = 10 ;
596 for ( my $i = 0 ; $i < $MAX_LXC_NETWORKS ; $i++ ) {
597 $confdesc ->{ "net $i " } = {
599 type
=> 'string' , format
=> 'pve-lxc-network' ,
600 description
=> "Specifies network interfaces for the container. \n\n " .
601 "The string should have the follow format: \n\n " .
602 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>] \n " .
603 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>] \n " .
604 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>] \n " .
605 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]" ,
612 return defined ( $confdesc ->{ $name });
615 # add JSON properties for create and set function
616 sub json_config_properties
{
619 foreach my $opt ( keys %$confdesc ) {
620 $prop ->{ $opt } = $confdesc ->{ $opt };
626 # container status helpers
628 sub list_active_containers
{
630 my $filename = "/proc/net/unix" ;
632 # similar test is used by lcxcontainers.c: list_active_containers
635 my $fh = IO
:: File-
> new ( $filename, "r" );
638 while ( defined ( my $line = < $fh >)) {
639 if ( $line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/ ) {
641 if ( $path =~ m!^@/etc/pve/lxc/(\d+)/command$! ) {
652 # warning: this is slow
656 my $active_hash = list_active_containers
();
658 return 1 if defined ( $active_hash ->{ $vmid });
663 sub get_container_disk_usage
{
666 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '--' , 'df' , '-P' , '-B' , '1' , '/' ];
676 if ( my ( $fsid, $total, $used, $avail ) = $line =~
677 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/ ) {
685 eval { PVE
:: Tools
:: run_command
( $cmd, timeout
=> 1 , outfunc
=> $parser ); };
694 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
696 my $active_hash = list_active_containers
();
698 foreach my $vmid ( keys %$list ) {
699 my $d = $list ->{ $vmid };
701 my $running = defined ( $active_hash ->{ $vmid });
703 $d ->{ status
} = $running ?
'running' : 'stopped' ;
705 my $cfspath = cfs_config_path
( $vmid );
706 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath ) || {};
708 $d ->{ name
} = $conf ->{ 'lxc.utsname' } || "CT $vmid " ;
709 $d ->{ name
} =~ s/[\s]//g ;
713 my $cfs_period_us = $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
714 my $cfs_quota_us = $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
716 if ( $cfs_period_us && $cfs_quota_us ) {
717 $d ->{ cpus
} = int ( $cfs_quota_us/$cfs_period_us );
721 $d ->{ maxdisk
} = defined ( $conf ->{ 'pve.disksize' }) ?
722 int ( $conf ->{ 'pve.disksize' }* 1024 * 1024 )* 1024 : 1024 * 1024 * 1024 * 1024 * 1024 ;
724 if ( my $private = $conf ->{ 'lxc.rootfs' }) {
725 if ( $private =~ m!^/! ) {
726 my $res = PVE
:: Tools
:: df
( $private, 2 );
727 $d ->{ disk
} = $res ->{ used
};
728 $d ->{ maxdisk
} = $res ->{ total
};
730 if ( $private =~ m!^(?:loop|nbd):(?:\S+)$! ) {
731 my $res = get_container_disk_usage
( $vmid );
732 $d ->{ disk
} = $res ->{ used
};
733 $d ->{ maxdisk
} = $res ->{ total
};
740 $d ->{ maxmem
} = ( $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }|| 0 ) +
741 ( $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }|| 0 );
753 foreach my $vmid ( keys %$list ) {
754 my $d = $list ->{ $vmid };
755 next if $d ->{ status
} ne 'running' ;
757 $d ->{ uptime
} = 100 ; # fixme:
759 $d ->{ mem
} = read_cgroup_value
( 'memory' , $vmid, 'memory.usage_in_bytes' );
760 $d ->{ swap
} = read_cgroup_value
( 'memory' , $vmid, 'memory.memsw.usage_in_bytes' ) - $d ->{ mem
};
762 my $blkio_bytes = read_cgroup_value
( 'blkio' , $vmid, 'blkio.throttle.io_service_bytes' , 1 );
763 my @bytes = split ( /\n/ , $blkio_bytes );
764 foreach my $byte ( @bytes ) {
765 if ( my ( $key, $value ) = $byte =~ /(Read|Write)\s+(\d+)/ ) {
766 $d ->{ diskread
} = $2 if $key eq 'Read' ;
767 $d ->{ diskwrite
} = $2 if $key eq 'Write' ;
776 sub print_lxc_network
{
779 die "no network name defined \n " if ! $net ->{ name
};
781 my $res = "name= $net ->{name}" ;
783 foreach my $k ( qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag) ) {
784 next if ! defined ( $net ->{ $k });
785 $res .= ", $k = $net ->{ $k }" ;
791 sub parse_lxc_network
{
796 return $res if ! $data ;
798 foreach my $pv ( split ( /,/ , $data )) {
799 if ( $pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/ ) {
806 $res ->{ type
} = 'veth' ;
807 $res ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $res ->{ hwaddr
};
812 sub read_cgroup_value
{
813 my ( $group, $vmid, $name, $full ) = @_ ;
815 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
817 return PVE
:: Tools
:: file_get_contents
( $path ) if $full ;
819 return PVE
:: Tools
:: file_read_firstline
( $path );
822 sub write_cgroup_value
{
823 my ( $group, $vmid, $name, $value ) = @_ ;
825 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
826 PVE
:: ProcFSTools
:: write_proc_entry
( $path, $value ) if - e
$path ;
830 sub find_lxc_console_pids
{
834 PVE
:: Tools
:: dir_glob_foreach
( '/proc' , '\d+' , sub {
837 my $cmdline = PVE
:: Tools
:: file_read_firstline
( "/proc/ $pid/cmdline " );
840 my @args = split ( /\0/ , $cmdline );
842 # serach for lxc-console -n <vmid>
843 return if scalar ( @args ) != 3 ;
844 return if $args [ 1 ] ne '-n' ;
845 return if $args [ 2 ] !~ m/^\d+$/ ;
846 return if $args [ 0 ] !~ m
|^( /usr/ bin
/) ?lxc-console
$|;
850 push @{ $res ->{ $vmid }}, $pid ;
862 $pid = $1 if $line =~ m/PID:\s+(\d+)/ ;
864 PVE
:: Tools
:: run_command
([ 'lxc-info' , '-n' , $vmid ], outfunc
=> $parser );
869 my $ipv4_reverse_mask = [
905 # Note: we cannot use Net:IP, because that only allows strict
907 sub parse_ipv4_cidr
{
908 my ( $cidr, $noerr ) = @_ ;
910 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
911 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
914 return undef if $noerr ;
916 die "unable to parse ipv4 address/mask \n " ;
919 sub lxc_conf_to_pve
{
920 my ( $vmid, $lxc_conf ) = @_ ;
922 my $properties = json_config_properties
();
924 my $conf = { digest
=> $lxc_conf ->{ digest
} };
926 foreach my $k ( keys %$properties ) {
928 if ( $k eq 'description' ) {
929 if ( my $raw = $lxc_conf ->{ 'pve.comment' }) {
930 $conf ->{ $k } = PVE
:: Tools
:: decode_text
( $raw );
932 } elsif ( $k eq 'onboot' ) {
933 $conf ->{ $k } = $lxc_conf ->{ 'pve.onboot' } if $lxc_conf ->{ 'pve.onboot' };
934 } elsif ( $k eq 'startup' ) {
935 $conf ->{ $k } = $lxc_conf ->{ 'pve.startup' } if $lxc_conf ->{ 'pve.startup' };
936 } elsif ( $k eq 'hostname' ) {
937 $conf ->{ $k } = $lxc_conf ->{ 'lxc.utsname' } if $lxc_conf ->{ 'lxc.utsname' };
938 } elsif ( $k eq 'nameserver' ) {
939 $conf ->{ $k } = $lxc_conf ->{ 'pve.nameserver' } if $lxc_conf ->{ 'pve.nameserver' };
940 } elsif ( $k eq 'searchdomain' ) {
941 $conf ->{ $k } = $lxc_conf ->{ 'pve.searchdomain' } if $lxc_conf ->{ 'pve.searchdomain' };
942 } elsif ( $k eq 'memory' ) {
943 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }) {
944 $conf ->{ $k } = int ( $value / ( 1024 * 1024 ));
946 } elsif ( $k eq 'swap' ) {
947 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }) {
948 my $mem = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } || 0 ;
949 $conf ->{ $k } = int (( $value - $mem ) / ( 1024 * 1024 ));
951 } elsif ( $k eq 'cpulimit' ) {
952 my $cfs_period_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
953 my $cfs_quota_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
955 if ( $cfs_period_us && $cfs_quota_us ) {
956 $conf ->{ $k } = $cfs_quota_us/$cfs_period_us ;
960 } elsif ( $k eq 'cpuunits' ) {
961 $conf ->{ $k } = $lxc_conf ->{ 'lxc.cgroup.cpu.shares' } || 1024 ;
962 } elsif ( $k eq 'disk' ) {
963 $conf ->{ $k } = defined ( $lxc_conf ->{ 'pve.disksize' }) ?
964 $lxc_conf ->{ 'pve.disksize' } : 0 ;
965 } elsif ( $k =~ m/^net\d$/ ) {
966 my $net = $lxc_conf ->{ $k };
968 $conf ->{ $k } = print_lxc_network
( $net );
975 # verify and cleanup nameserver list (replace \0 with ' ')
976 sub verify_nameserver_list
{
977 my ( $nameserver_list ) = @_ ;
980 foreach my $server ( PVE
:: Tools
:: split_list
( $nameserver_list )) {
981 PVE
:: JSONSchema
:: pve_verify_ip
( $server );
985 return join ( ' ' , @list );
988 sub verify_searchdomain_list
{
989 my ( $searchdomain_list ) = @_ ;
992 foreach my $server ( PVE
:: Tools
:: split_list
( $searchdomain_list )) {
993 # todo: should we add checks for valid dns domains?
997 return join ( ' ' , @list );
1000 sub update_lxc_config
{
1001 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
1005 my $rootdir = undef ;
1007 my $pid = find_lxc_pid
( $vmid );
1008 $rootdir = "/proc/ $pid/root/ " ;
1011 if ( defined ( $delete )) {
1012 foreach my $opt ( @$delete ) {
1013 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
1014 die "unable to delete required option ' $opt ' \n " ;
1015 } elsif ( $opt eq 'swap' ) {
1016 delete $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' };
1017 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , - 1 );
1018 } elsif ( $opt eq 'description' ) {
1019 delete $conf ->{ 'pve.comment' };
1020 } elsif ( $opt eq 'onboot' ) {
1021 delete $conf ->{ 'pve.onboot' };
1022 } elsif ( $opt eq 'startup' ) {
1023 delete $conf ->{ 'pve.startup' };
1024 } elsif ( $opt eq 'nameserver' ) {
1025 delete $conf ->{ 'pve.nameserver' };
1026 push @nohotplug, $opt ;
1028 } elsif ( $opt eq 'searchdomain' ) {
1029 delete $conf ->{ 'pve.searchdomain' };
1030 push @nohotplug, $opt ;
1032 } elsif ( $opt =~ m/^net(\d)$/ ) {
1033 delete $conf ->{ $opt };
1036 PVE
:: Network
:: veth_delete
( "veth${vmid}. $netid " );
1040 PVE
:: LXC
:: write_config
( $vmid, $conf ) if $running ;
1044 foreach my $opt ( keys %$param ) {
1045 my $value = $param ->{ $opt };
1046 if ( $opt eq 'hostname' ) {
1047 $conf ->{ 'lxc.utsname' } = $value ;
1049 } elsif ( $opt eq 'onboot' ) {
1050 $conf ->{ 'pve.onboot' } = $value ?
1 : 0 ;
1051 } elsif ( $opt eq 'startup' ) {
1052 $conf ->{ 'pve.startup' } = $value ;
1053 } elsif ( $opt eq 'nameserver' ) {
1054 my $list = verify_nameserver_list
( $value );
1055 $conf ->{ 'pve.nameserver' } = $list ;
1056 push @nohotplug, $opt ;
1058 } elsif ( $opt eq 'searchdomain' ) {
1059 my $list = verify_searchdomain_list
( $value );
1060 $conf ->{ 'pve.searchdomain' } = $list ;
1061 push @nohotplug, $opt ;
1063 } elsif ( $opt eq 'memory' ) {
1064 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
1065 write_cgroup_value
( "memory" , $vmid, "memory.limit_in_bytes" , $value*1024*1024 );
1066 } elsif ( $opt eq 'swap' ) {
1067 my $mem = $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' };
1068 $mem = $param ->{ memory
}* 1024 * 1024 if $param ->{ memory
};
1069 $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' } = $mem + $value*1024*1024 ;
1070 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , $mem + $value*1024*1024 );
1072 } elsif ( $opt eq 'cpulimit' ) {
1074 my $cfs_period_us = 100000 ;
1075 $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' } = $cfs_period_us ;
1076 $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' } = $cfs_period_us*$value ;
1077 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , $cfs_period_us*$value );
1079 delete $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
1080 delete $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
1081 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , - 1 );
1083 } elsif ( $opt eq 'cpuunits' ) {
1084 $conf ->{ 'lxc.cgroup.cpu.shares' } = $value ;
1085 write_cgroup_value
( "cpu" , $vmid, "cpu.shares" , $value );
1086 } elsif ( $opt eq 'description' ) {
1087 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
1088 } elsif ( $opt eq 'disk' ) {
1089 $conf ->{ 'pve.disksize' } = $value ;
1090 push @nohotplug, $opt ;
1092 } elsif ( $opt =~ m/^net(\d+)$/ ) {
1094 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
1095 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
1097 $conf ->{ $opt } = $net ;
1099 update_net
( $vmid, $conf, $opt, $net, $netid, $rootdir );
1105 PVE
:: LXC
:: write_config
( $vmid, $conf ) if $running ;
1108 if ( $running && scalar ( @nohotplug )) {
1109 die "unable to modify " . join ( ',' , @nohotplug ) . " while container is running \n " ;
1113 sub get_primary_ips
{
1116 # return data from net0
1118 my $net = $conf ->{ net0
};
1119 return undef if ! $net ;
1121 my $ipv4 = $net ->{ ip
};
1122 $ipv4 =~ s!/\d+$!! if $ipv4 ;
1123 my $ipv6 = $net ->{ ip
};
1124 $ipv6 =~ s!/\d+$!! if $ipv6 ;
1126 return ( $ipv4, $ipv6 );
1129 sub destory_lxc_container
{
1130 my ( $storage_cfg, $vmid, $conf ) = @_ ;
1132 if ( my $volid = $conf ->{ 'pve.volid' }) {
1134 my ( $vtype, $name, $owner ) = PVE
:: Storage
:: parse_volname
( $storage_cfg, $volid );
1135 die "got strange volid (containe is not owner!) \n " if $vmid != $owner ;
1137 PVE
:: Storage
:: vdisk_free
( $storage_cfg, $volid );
1139 destroy_config
( $vmid );
1142 my $cmd = [ 'lxc-destroy' , '-n' , $vmid ];
1144 PVE
:: Tools
:: run_command
( $cmd );
1148 my $safe_num_ne = sub {
1151 return 0 if ! defined ( $a ) && ! defined ( $b );
1152 return 1 if ! defined ( $a );
1153 return 1 if ! defined ( $b );
1158 my $safe_string_ne = sub {
1161 return 0 if ! defined ( $a ) && ! defined ( $b );
1162 return 1 if ! defined ( $a );
1163 return 1 if ! defined ( $b );
1169 my ( $vmid, $conf, $opt, $newnet, $netid, $rootdir ) = @_ ;
1171 my $veth = $newnet ->{ 'veth.pair' };
1172 my $vethpeer = $veth . "p" ;
1173 my $eth = $newnet ->{ name
};
1175 if ( $conf ->{ $opt }) {
1176 if (& $safe_string_ne ( $conf ->{ $opt }->{ hwaddr
}, $newnet ->{ hwaddr
}) ||
1177 & $safe_string_ne ( $conf ->{ $opt }->{ name
}, $newnet ->{ name
})) {
1179 PVE
:: Network
:: veth_delete
( $veth );
1180 delete $conf ->{ $opt };
1181 PVE
:: LXC
:: write_config
( $vmid, $conf );
1183 hotplug_net
( $vmid, $conf, $opt, $newnet );
1185 } elsif (& $safe_string_ne ( $conf ->{ $opt }->{ bridge
}, $newnet ->{ bridge
}) ||
1186 & $safe_num_ne ( $conf ->{ $opt }->{ tag
}, $newnet ->{ tag
}) ||
1187 & $safe_num_ne ( $conf ->{ $opt }->{ firewall
}, $newnet ->{ firewall
})) {
1189 if ( $conf ->{ $opt }->{ bridge
}){
1190 PVE
:: Network
:: tap_unplug
( $veth );
1191 delete $conf ->{ $opt }->{ bridge
};
1192 delete $conf ->{ $opt }->{ tag
};
1193 delete $conf ->{ $opt }->{ firewall
};
1194 PVE
:: LXC
:: write_config
( $vmid, $conf );
1197 PVE
:: Network
:: tap_plug
( $veth, $newnet ->{ bridge
}, $newnet ->{ tag
}, $newnet ->{ firewall
});
1198 $conf ->{ $opt }->{ bridge
} = $newnet ->{ bridge
} if $newnet ->{ bridge
};
1199 $conf ->{ $opt }->{ tag
} = $newnet ->{ tag
} if $newnet ->{ tag
};
1200 $conf ->{ $opt }->{ firewall
} = $newnet ->{ firewall
} if $newnet ->{ firewall
};
1201 PVE
:: LXC
:: write_config
( $vmid, $conf );
1204 hotplug_net
( $vmid, $conf, $opt, $newnet );
1207 update_ipconfig
( $vmid, $conf, $opt, $eth, $newnet, $rootdir );
1211 my ( $vmid, $conf, $opt, $newnet ) = @_ ;
1213 my $veth = $newnet ->{ 'veth.pair' };
1214 my $vethpeer = $veth . "p" ;
1215 my $eth = $newnet ->{ name
};
1217 PVE
:: Network
:: veth_create
( $veth, $vethpeer, $newnet ->{ bridge
}, $newnet ->{ hwaddr
});
1218 PVE
:: Network
:: tap_plug
( $veth, $newnet ->{ bridge
}, $newnet ->{ tag
}, $newnet ->{ firewall
});
1220 #attach peer in container
1221 my $cmd = [ 'lxc-device' , '-n' , $vmid, 'add' , $vethpeer, " $eth " ];
1222 PVE
:: Tools
:: run_command
( $cmd );
1224 #link up peer in container
1225 $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'link' , 'set' , $eth , 'up' ];
1226 PVE
:: Tools
:: run_command
( $cmd );
1228 $conf ->{ $opt }->{ type
} = 'veth' ;
1229 $conf ->{ $opt }->{ bridge
} = $newnet ->{ bridge
} if $newnet ->{ bridge
};
1230 $conf ->{ $opt }->{ tag
} = $newnet ->{ tag
} if $newnet ->{ tag
};
1231 $conf ->{ $opt }->{ firewall
} = $newnet ->{ firewall
} if $newnet ->{ firewall
};
1232 $conf ->{ $opt }->{ hwaddr
} = $newnet ->{ hwaddr
} if $newnet ->{ hwaddr
};
1233 $conf ->{ $opt }->{ name
} = $newnet ->{ name
} if $newnet ->{ name
};
1234 $conf ->{ $opt }->{ 'veth.pair' } = $newnet ->{ 'veth.pair' } if $newnet ->{ 'veth.pair' };
1236 delete $conf ->{ $opt }->{ ip
} if $conf ->{ $opt }->{ ip
};
1237 delete $conf ->{ $opt }->{ ip6
} if $conf ->{ $opt }->{ ip6
};
1238 delete $conf ->{ $opt }->{ gw
} if $conf ->{ $opt }->{ gw
};
1239 delete $conf ->{ $opt }->{ gw6
} if $conf ->{ $opt }->{ gw6
};
1241 PVE
:: LXC
:: write_config
( $vmid, $conf );
1244 sub update_ipconfig
{
1245 my ( $vmid, $conf, $opt, $eth, $newnet, $rootdir ) = @_ ;
1247 my $lxc_setup = PVE
:: LXCSetup-
> new ( $conf, $rootdir );
1249 if (& $safe_string_ne ( $conf ->{ $opt }->{ ip
}, $newnet ->{ ip
})) {
1251 if ( $conf ->{ $opt }->{ ip
}) {
1252 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'addr' , 'del' , $conf ->{ $opt }->{ ip
}, 'dev' , $eth ];
1253 PVE
:: Tools
:: run_command
( $cmd );
1255 delete $conf ->{ $opt }->{ ip
};
1256 PVE
:: LXC
:: write_config
( $vmid, $conf );
1257 $lxc_setup -> setup_network ( $conf );
1260 if ( $newnet ->{ ip
}) {
1261 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'addr' , 'add' , $newnet ->{ ip
}, 'dev' , $eth ];
1262 PVE
:: Tools
:: run_command
( $cmd );
1264 $conf ->{ $opt }->{ ip
} = $newnet ->{ ip
};
1265 PVE
:: LXC
:: write_config
( $vmid, $conf );
1266 $lxc_setup -> setup_network ( $conf );
1271 if (& $safe_string_ne ( $conf ->{ $opt }->{ gw
}, $newnet ->{ gw
})) {
1273 if ( $conf ->{ $opt }->{ gw
}) {
1274 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'route' , 'del' , 'default' , 'via' , $conf ->{ $opt }->{ gw
} ];
1275 PVE
:: Tools
:: run_command
( $cmd );
1277 delete $conf ->{ $opt }->{ gw
};
1278 PVE
:: LXC
:: write_config
( $vmid, $conf );
1279 $lxc_setup -> setup_network ( $conf );
1283 if ( $newnet ->{ gw
}) {
1284 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'route' , 'add' , 'default' , 'via' , $newnet ->{ gw
} ];
1285 PVE
:: Tools
:: run_command
( $cmd );
1287 $conf ->{ $opt }->{ gw
} = $newnet ->{ gw
};
1288 PVE
:: LXC
:: write_config
( $vmid, $conf );
1289 $lxc_setup -> setup_network ( $conf );
1293 if (& $safe_string_ne ( $conf ->{ $opt }->{ ip6
}, $newnet ->{ ip6
})) {
1295 if ( $conf ->{ $opt }->{ ip6
}) {
1296 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'addr' , 'del' , $conf ->{ $opt }->{ ip6
}, 'dev' , $eth ];
1297 PVE
:: Tools
:: run_command
( $cmd );
1299 delete $conf ->{ $opt }->{ ip6
};
1300 PVE
:: LXC
:: write_config
( $vmid, $conf );
1301 $lxc_setup -> setup_network ( $conf );
1304 if ( $newnet ->{ ip6
}) {
1305 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'addr' , 'add' , $newnet ->{ ip6
}, 'dev' , $eth ];
1306 PVE
:: Tools
:: run_command
( $cmd );
1308 $conf ->{ $opt }->{ ip6
} = $newnet ->{ ip6
};
1309 PVE
:: LXC
:: write_config
( $vmid, $conf );
1310 $lxc_setup -> setup_network ( $conf );
1315 if (& $safe_string_ne ( $conf ->{ $opt }->{ gw6
}, $newnet ->{ gw6
})) {
1317 if ( $conf ->{ $opt }->{ gw6
}) {
1318 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'route' , 'del' , 'default' , 'via' , $conf ->{ $opt }->{ gw6
} ];
1319 PVE
:: Tools
:: run_command
( $cmd );
1321 delete $conf ->{ $opt }->{ gw6
};
1322 PVE
:: LXC
:: write_config
( $vmid, $conf );
1323 $lxc_setup -> setup_network ( $conf );
1327 if ( $newnet ->{ gw6
}) {
1328 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'route' , 'add' , 'default' , 'via' , $newnet ->{ gw6
} ];
1329 PVE
:: Tools
:: run_command
( $cmd );
1331 $conf ->{ $opt }->{ gw6
} = $newnet ->{ gw6
};
1332 PVE
:: LXC
:: write_config
( $vmid, $conf );
1333 $lxc_setup -> setup_network ( $conf );