]>
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 bridge defined \n " if ! $net ->{ bridge
};
781 my $res = "bridge= $net ->{bridge}" ;
783 foreach my $k ( qw(hwaddr mtu name 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 ;
856 my $ipv4_reverse_mask = [
892 # Note: we cannot use Net:IP, because that only allows strict
894 sub parse_ipv4_cidr
{
895 my ( $cidr, $noerr ) = @_ ;
897 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
898 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
901 return undef if $noerr ;
903 die "unable to parse ipv4 address/mask \n " ;
906 sub lxc_conf_to_pve
{
907 my ( $vmid, $lxc_conf ) = @_ ;
909 my $properties = json_config_properties
();
911 my $conf = { digest
=> $lxc_conf ->{ digest
} };
913 foreach my $k ( keys %$properties ) {
915 if ( $k eq 'description' ) {
916 if ( my $raw = $lxc_conf ->{ 'pve.comment' }) {
917 $conf ->{ $k } = PVE
:: Tools
:: decode_text
( $raw );
919 } elsif ( $k eq 'onboot' ) {
920 $conf ->{ $k } = $lxc_conf ->{ 'pve.onboot' } if $lxc_conf ->{ 'pve.onboot' };
921 } elsif ( $k eq 'startup' ) {
922 $conf ->{ $k } = $lxc_conf ->{ 'pve.startup' } if $lxc_conf ->{ 'pve.startup' };
923 } elsif ( $k eq 'hostname' ) {
924 $conf ->{ $k } = $lxc_conf ->{ 'lxc.utsname' } if $lxc_conf ->{ 'lxc.utsname' };
925 } elsif ( $k eq 'nameserver' ) {
926 $conf ->{ $k } = $lxc_conf ->{ 'pve.nameserver' } if $lxc_conf ->{ 'pve.nameserver' };
927 } elsif ( $k eq 'searchdomain' ) {
928 $conf ->{ $k } = $lxc_conf ->{ 'pve.searchdomain' } if $lxc_conf ->{ 'pve.searchdomain' };
929 } elsif ( $k eq 'memory' ) {
930 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }) {
931 $conf ->{ $k } = int ( $value / ( 1024 * 1024 ));
933 } elsif ( $k eq 'swap' ) {
934 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }) {
935 my $mem = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } || 0 ;
936 $conf ->{ $k } = int (( $value - $mem ) / ( 1024 * 1024 ));
938 } elsif ( $k eq 'cpulimit' ) {
939 my $cfs_period_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
940 my $cfs_quota_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
942 if ( $cfs_period_us && $cfs_quota_us ) {
943 $conf ->{ $k } = $cfs_quota_us/$cfs_period_us ;
947 } elsif ( $k eq 'cpuunits' ) {
948 $conf ->{ $k } = $lxc_conf ->{ 'lxc.cgroup.cpu.shares' } || 1024 ;
949 } elsif ( $k eq 'disk' ) {
950 $conf ->{ $k } = defined ( $lxc_conf ->{ 'pve.disksize' }) ?
951 $lxc_conf ->{ 'pve.disksize' } : 0 ;
952 } elsif ( $k =~ m/^net\d$/ ) {
953 my $net = $lxc_conf ->{ $k };
955 $conf ->{ $k } = print_lxc_network
( $net );
962 # verify and cleanup nameserver list (replace \0 with ' ')
963 sub verify_nameserver_list
{
964 my ( $nameserver_list ) = @_ ;
967 foreach my $server ( PVE
:: Tools
:: split_list
( $nameserver_list )) {
968 PVE
:: JSONSchema
:: pve_verify_ip
( $server );
972 return join ( ' ' , @list );
975 sub verify_searchdomain_list
{
976 my ( $searchdomain_list ) = @_ ;
979 foreach my $server ( PVE
:: Tools
:: split_list
( $searchdomain_list )) {
980 # todo: should we add checks for valid dns domains?
984 return join ( ' ' , @list );
987 sub update_lxc_config
{
988 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
992 if ( defined ( $delete )) {
993 foreach my $opt ( @$delete ) {
994 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
995 die "unable to delete required option ' $opt ' \n " ;
996 } elsif ( $opt eq 'swap' ) {
997 delete $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' };
998 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , - 1 );
999 } elsif ( $opt eq 'description' ) {
1000 delete $conf ->{ 'pve.comment' };
1001 } elsif ( $opt eq 'onboot' ) {
1002 delete $conf ->{ 'pve.onboot' };
1003 } elsif ( $opt eq 'startup' ) {
1004 delete $conf ->{ 'pve.startup' };
1005 } elsif ( $opt eq 'nameserver' ) {
1006 delete $conf ->{ 'pve.nameserver' };
1007 push @nohotplug, $opt ;
1009 } elsif ( $opt eq 'searchdomain' ) {
1010 delete $conf ->{ 'pve.searchdomain' };
1011 push @nohotplug, $opt ;
1013 } elsif ( $opt =~ m/^net(\d)$/ ) {
1014 delete $conf ->{ $opt };
1017 PVE
:: Network
:: veth_delete
( "veth${vmid}. $netid " );
1021 PVE
:: LXC
:: write_config
( $vmid, $conf ) if $running ;
1025 foreach my $opt ( keys %$param ) {
1026 my $value = $param ->{ $opt };
1027 if ( $opt eq 'hostname' ) {
1028 $conf ->{ 'lxc.utsname' } = $value ;
1029 } elsif ( $opt eq 'onboot' ) {
1030 $conf ->{ 'pve.onboot' } = $value ?
1 : 0 ;
1031 } elsif ( $opt eq 'startup' ) {
1032 $conf ->{ 'pve.startup' } = $value ;
1033 } elsif ( $opt eq 'nameserver' ) {
1034 my $list = verify_nameserver_list
( $value );
1035 $conf ->{ 'pve.nameserver' } = $list ;
1036 push @nohotplug, $opt ;
1038 } elsif ( $opt eq 'searchdomain' ) {
1039 my $list = verify_searchdomain_list
( $value );
1040 $conf ->{ 'pve.searchdomain' } = $list ;
1041 push @nohotplug, $opt ;
1043 } elsif ( $opt eq 'memory' ) {
1044 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
1045 write_cgroup_value
( "memory" , $vmid, "memory.limit_in_bytes" , $value*1024*1024 );
1046 } elsif ( $opt eq 'swap' ) {
1047 my $mem = $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' };
1048 $mem = $param ->{ memory
}* 1024 * 1024 if $param ->{ memory
};
1049 $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' } = $mem + $value*1024*1024 ;
1050 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , $mem + $value*1024*1024 );
1052 } elsif ( $opt eq 'cpulimit' ) {
1054 my $cfs_period_us = 100000 ;
1055 $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' } = $cfs_period_us ;
1056 $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' } = $cfs_period_us*$value ;
1057 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , $cfs_period_us*$value );
1059 delete $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
1060 delete $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
1061 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , - 1 );
1063 } elsif ( $opt eq 'cpuunits' ) {
1064 $conf ->{ 'lxc.cgroup.cpu.shares' } = $value ;
1065 write_cgroup_value
( "cpu" , $vmid, "cpu.shares" , $value );
1066 } elsif ( $opt eq 'description' ) {
1067 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
1068 } elsif ( $opt eq 'disk' ) {
1069 $conf ->{ 'pve.disksize' } = $value ;
1070 push @nohotplug, $opt ;
1072 } elsif ( $opt =~ m/^net(\d+)$/ ) {
1074 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
1075 my $oldnet = $conf ->{ $opt } ?
$conf ->{ $opt } : undef ;
1076 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
1077 $conf ->{ $opt } = $net ;
1079 update_net
( $vmid, $net, $oldnet, $netid );
1083 PVE
:: LXC
:: write_config
( $vmid, $conf ) if $running ;
1086 if ( $running && scalar ( @nohotplug )) {
1087 die "unable to modify " . join ( ',' , @nohotplug ) . " while container is running \n " ;
1091 sub get_primary_ips
{
1094 # return data from net0
1096 my $net = $conf ->{ net0
};
1097 return undef if ! $net ;
1099 my $ipv4 = $net ->{ ip
};
1100 $ipv4 =~ s!/\d+$!! if $ipv4 ;
1101 my $ipv6 = $net ->{ ip
};
1102 $ipv6 =~ s!/\d+$!! if $ipv6 ;
1104 return ( $ipv4, $ipv6 );
1107 sub destory_lxc_container
{
1108 my ( $storage_cfg, $vmid, $conf ) = @_ ;
1110 if ( my $volid = $conf ->{ 'pve.volid' }) {
1112 my ( $vtype, $name, $owner ) = PVE
:: Storage
:: parse_volname
( $storage_cfg, $volid );
1113 die "got strange volid (containe is not owner!) \n " if $vmid != $owner ;
1115 PVE
:: Storage
:: vdisk_free
( $storage_cfg, $volid );
1117 destroy_config
( $vmid );
1120 my $cmd = [ 'lxc-destroy' , '-n' , $vmid ];
1122 PVE
:: Tools
:: run_command
( $cmd );
1126 my $safe_num_ne = sub {
1129 return 0 if ! defined ( $a ) && ! defined ( $b );
1130 return 1 if ! defined ( $a );
1131 return 1 if ! defined ( $b );
1136 my $safe_string_ne = sub {
1139 return 0 if ! defined ( $a ) && ! defined ( $b );
1140 return 1 if ! defined ( $a );
1141 return 1 if ! defined ( $b );
1147 my ( $vmid, $newnet, $oldnet, $netid ) = @_ ;
1149 my $veth = $newnet ->{ 'veth.pair' };
1150 my $vethpeer = $veth . "p" ;
1151 my $eth = $newnet ->{ name
};
1154 if (& $safe_string_ne ( $oldnet ->{ hwaddr
}, $newnet ->{ hwaddr
}) ||
1155 & $safe_string_ne ( $oldnet ->{ name
}, $newnet ->{ name
})) {
1157 PVE
:: Network
:: veth_delete
( $veth );
1158 hotplug_net
( $vmid, $newnet );
1160 } elsif (& $safe_string_ne ( $oldnet ->{ bridge
}, $newnet ->{ bridge
}) ||
1161 & $safe_num_ne ( $oldnet ->{ tag
}, $newnet ->{ tag
}) ||
1162 & $safe_num_ne ( $oldnet ->{ firewall
}, $newnet ->{ firewall
})) {
1164 PVE
:: Network
:: tap_unplug
( $veth );
1165 PVE
:: Network
:: tap_plug
( $veth, $newnet ->{ bridge
}, $newnet ->{ tag
}, $newnet ->{ firewall
});
1168 hotplug_net
( $vmid, $newnet );
1171 update_ipconfig
( $vmid, $eth, $oldnet, $newnet );
1175 my ( $vmid, $newnet ) = @_ ;
1177 my $veth = $newnet ->{ 'veth.pair' };
1178 my $vethpeer = $veth . "p" ;
1179 my $eth = $newnet ->{ name
};
1181 PVE
:: Network
:: veth_create
( $veth, $vethpeer, $newnet ->{ bridge
}, $newnet ->{ hwaddr
});
1182 PVE
:: Network
:: tap_plug
( $veth, $newnet ->{ bridge
}, $newnet ->{ tag
}, $newnet ->{ firewall
});
1184 #attach peer in container
1185 my $cmd = [ 'lxc-device' , '-n' , $vmid, 'add' , $vethpeer, " $eth " ];
1186 PVE
:: Tools
:: run_command
( $cmd );
1188 #link up peer in container
1189 $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'link' , 'set' , $eth , 'up' ];
1190 PVE
:: Tools
:: run_command
( $cmd );
1193 sub update_ipconfig
{
1194 my ( $vmid, $eth, $oldnet, $newnet ) = @_ ;
1197 if (& $safe_string_ne ( $oldnet ->{ ip
}, $newnet ->{ ip
})) {
1198 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'addr' , 'add' , $newnet ->{ ip
}, 'dev' , $eth ];
1199 PVE
:: Tools
:: run_command
( $cmd );
1202 if (& $safe_string_ne ( $oldnet ->{ gw
}, $newnet ->{ gw
})) {
1203 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'route' , 'add' , 'default' , 'via' , $newnet ->{ gw
} ];
1204 PVE
:: Tools
:: run_command
( $cmd );
1206 #fix me : ip,gateway removal
1208 #fix me : write /etc/network/interfaces ?