]>
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 );
866 die "unable to get PID for CT $vmid (not running?) \n " if ! $pid ;
871 my $ipv4_reverse_mask = [
907 # Note: we cannot use Net:IP, because that only allows strict
909 sub parse_ipv4_cidr
{
910 my ( $cidr, $noerr ) = @_ ;
912 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
913 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
916 return undef if $noerr ;
918 die "unable to parse ipv4 address/mask \n " ;
921 sub lxc_conf_to_pve
{
922 my ( $vmid, $lxc_conf ) = @_ ;
924 my $properties = json_config_properties
();
926 my $conf = { digest
=> $lxc_conf ->{ digest
} };
928 foreach my $k ( keys %$properties ) {
930 if ( $k eq 'description' ) {
931 if ( my $raw = $lxc_conf ->{ 'pve.comment' }) {
932 $conf ->{ $k } = PVE
:: Tools
:: decode_text
( $raw );
934 } elsif ( $k eq 'onboot' ) {
935 $conf ->{ $k } = $lxc_conf ->{ 'pve.onboot' } if $lxc_conf ->{ 'pve.onboot' };
936 } elsif ( $k eq 'startup' ) {
937 $conf ->{ $k } = $lxc_conf ->{ 'pve.startup' } if $lxc_conf ->{ 'pve.startup' };
938 } elsif ( $k eq 'hostname' ) {
939 $conf ->{ $k } = $lxc_conf ->{ 'lxc.utsname' } if $lxc_conf ->{ 'lxc.utsname' };
940 } elsif ( $k eq 'nameserver' ) {
941 $conf ->{ $k } = $lxc_conf ->{ 'pve.nameserver' } if $lxc_conf ->{ 'pve.nameserver' };
942 } elsif ( $k eq 'searchdomain' ) {
943 $conf ->{ $k } = $lxc_conf ->{ 'pve.searchdomain' } if $lxc_conf ->{ 'pve.searchdomain' };
944 } elsif ( $k eq 'memory' ) {
945 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }) {
946 $conf ->{ $k } = int ( $value / ( 1024 * 1024 ));
948 } elsif ( $k eq 'swap' ) {
949 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }) {
950 my $mem = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } || 0 ;
951 $conf ->{ $k } = int (( $value - $mem ) / ( 1024 * 1024 ));
953 } elsif ( $k eq 'cpulimit' ) {
954 my $cfs_period_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
955 my $cfs_quota_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
957 if ( $cfs_period_us && $cfs_quota_us ) {
958 $conf ->{ $k } = $cfs_quota_us/$cfs_period_us ;
962 } elsif ( $k eq 'cpuunits' ) {
963 $conf ->{ $k } = $lxc_conf ->{ 'lxc.cgroup.cpu.shares' } || 1024 ;
964 } elsif ( $k eq 'disk' ) {
965 $conf ->{ $k } = defined ( $lxc_conf ->{ 'pve.disksize' }) ?
966 $lxc_conf ->{ 'pve.disksize' } : 0 ;
967 } elsif ( $k =~ m/^net\d$/ ) {
968 my $net = $lxc_conf ->{ $k };
970 $conf ->{ $k } = print_lxc_network
( $net );
977 # verify and cleanup nameserver list (replace \0 with ' ')
978 sub verify_nameserver_list
{
979 my ( $nameserver_list ) = @_ ;
982 foreach my $server ( PVE
:: Tools
:: split_list
( $nameserver_list )) {
983 PVE
:: JSONSchema
:: pve_verify_ip
( $server );
987 return join ( ' ' , @list );
990 sub verify_searchdomain_list
{
991 my ( $searchdomain_list ) = @_ ;
994 foreach my $server ( PVE
:: Tools
:: split_list
( $searchdomain_list )) {
995 # todo: should we add checks for valid dns domains?
999 return join ( ' ' , @list );
1002 sub update_lxc_config
{
1003 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
1009 my $pid = find_lxc_pid
( $vmid );
1010 $rootdir = "/proc/ $pid/root " ;
1013 if ( defined ( $delete )) {
1014 foreach my $opt ( @$delete ) {
1015 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
1016 die "unable to delete required option ' $opt ' \n " ;
1017 } elsif ( $opt eq 'swap' ) {
1018 delete $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' };
1019 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , - 1 );
1020 } elsif ( $opt eq 'description' ) {
1021 delete $conf ->{ 'pve.comment' };
1022 } elsif ( $opt eq 'onboot' ) {
1023 delete $conf ->{ 'pve.onboot' };
1024 } elsif ( $opt eq 'startup' ) {
1025 delete $conf ->{ 'pve.startup' };
1026 } elsif ( $opt eq 'nameserver' ) {
1027 delete $conf ->{ 'pve.nameserver' };
1028 push @nohotplug, $opt ;
1030 } elsif ( $opt eq 'searchdomain' ) {
1031 delete $conf ->{ 'pve.searchdomain' };
1032 push @nohotplug, $opt ;
1034 } elsif ( $opt =~ m/^net(\d)$/ ) {
1035 delete $conf ->{ $opt };
1038 PVE
:: Network
:: veth_delete
( "veth${vmid}. $netid " );
1042 PVE
:: LXC
:: write_config
( $vmid, $conf ) if $running ;
1046 foreach my $opt ( keys %$param ) {
1047 my $value = $param ->{ $opt };
1048 if ( $opt eq 'hostname' ) {
1049 $conf ->{ 'lxc.utsname' } = $value ;
1050 } elsif ( $opt eq 'onboot' ) {
1051 $conf ->{ 'pve.onboot' } = $value ?
1 : 0 ;
1052 } elsif ( $opt eq 'startup' ) {
1053 $conf ->{ 'pve.startup' } = $value ;
1054 } elsif ( $opt eq 'nameserver' ) {
1055 my $list = verify_nameserver_list
( $value );
1056 $conf ->{ 'pve.nameserver' } = $list ;
1057 push @nohotplug, $opt ;
1059 } elsif ( $opt eq 'searchdomain' ) {
1060 my $list = verify_searchdomain_list
( $value );
1061 $conf ->{ 'pve.searchdomain' } = $list ;
1062 push @nohotplug, $opt ;
1064 } elsif ( $opt eq 'memory' ) {
1065 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
1066 write_cgroup_value
( "memory" , $vmid, "memory.limit_in_bytes" , $value*1024*1024 );
1067 } elsif ( $opt eq 'swap' ) {
1068 my $mem = $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' };
1069 $mem = $param ->{ memory
}* 1024 * 1024 if $param ->{ memory
};
1070 $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' } = $mem + $value*1024*1024 ;
1071 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , $mem + $value*1024*1024 );
1073 } elsif ( $opt eq 'cpulimit' ) {
1075 my $cfs_period_us = 100000 ;
1076 $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' } = $cfs_period_us ;
1077 $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' } = $cfs_period_us*$value ;
1078 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , $cfs_period_us*$value );
1080 delete $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
1081 delete $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
1082 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , - 1 );
1084 } elsif ( $opt eq 'cpuunits' ) {
1085 $conf ->{ 'lxc.cgroup.cpu.shares' } = $value ;
1086 write_cgroup_value
( "cpu" , $vmid, "cpu.shares" , $value );
1087 } elsif ( $opt eq 'description' ) {
1088 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
1089 } elsif ( $opt eq 'disk' ) {
1090 $conf ->{ 'pve.disksize' } = $value ;
1091 push @nohotplug, $opt ;
1093 } elsif ( $opt =~ m/^net(\d+)$/ ) {
1095 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
1096 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
1098 $conf ->{ $opt } = $net ;
1100 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 my $optdata = $conf ->{ $opt };
1253 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , @_ ];
1254 PVE
:: Tools
:: run_command
( $cmd );
1257 my $change_ip_config = sub {
1258 my ( $family_opt, $suffix ) = @_ ;
1259 $suffix = '' if ! $suffix ;
1260 my $gw = "gw $suffix " ;
1261 my $ip = "ip $suffix " ;
1263 my $change_ip = & $safe_string_ne ( $optdata ->{ $ip }, $newnet ->{ $ip });
1264 my $change_gw = & $safe_string_ne ( $optdata ->{ $gw }, $newnet ->{ $gw });
1266 return if ! $change_ip && ! $change_gw ;
1268 # step 1: add new IP, if this fails we cancel
1269 if ( $change_ip && $newnet ->{ $ip }) {
1270 eval { & $netcmd ( $family_opt, 'addr' , 'add' , $newnet ->{ $ip }, 'dev' , $eth ); };
1277 # step 2: replace gateway
1278 # If this fails we delete the added IP and cancel.
1279 # If it succeeds we save the config and delete the old IP, ignoring
1280 # errors. The config is then saved.
1281 # Note: 'ip route replace' can add
1283 if ( $newnet ->{ $gw }) {
1284 eval { & $netcmd ( $family_opt, 'route' , 'replace' , 'default' , 'via' , $newnet ->{ $gw }); };
1287 # the route was not replaced, the old IP is still available
1288 # rollback (delete new IP) and cancel
1290 eval { & $netcmd ( $family_opt, 'addr' , 'del' , $newnet ->{ $ip }, 'dev' , $eth ); };
1291 warn $@ if $@ ; # no need to die here
1296 eval { & $netcmd ( $family_opt, 'route' , 'del' , 'default' ); };
1297 # if the route was not deleted, the guest might have deleted it manually
1303 # from this point on we safe the configuration
1304 # step 3: delete old IP ignoring errors
1305 if ( $change_ip && $optdata ->{ $ip }) {
1306 eval { & $netcmd ( $family_opt, 'addr' , 'del' , $optdata ->{ $ip }, 'dev' , $eth ); };
1307 warn $@ if $@ ; # no need to die here
1310 foreach my $property ( $ip, $gw ) {
1311 if ( $newnet ->{ $property }) {
1312 $optdata ->{ $property } = $newnet ->{ $property };
1314 delete $optdata ->{ $property };
1317 PVE
:: LXC
:: write_config
( $vmid, $conf );
1318 $lxc_setup -> setup_network ( $conf );
1321 & $change_ip_config ( '-4' );
1322 & $change_ip_config ( '-6' , '6' );