]>
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 );
132 'pve.snapcomment' => 1 ,
134 'pve.snapstate' => 1 ,
138 my $valid_lxc_network_keys = {
141 name
=> 1 , # ifname inside container
142 'veth.pair' => 1 , # ifname at host (eth${vmid}.X)
146 my $valid_pve_network_keys = {
156 my $lxc_array_configs = {
161 'lxc.cgroup.devices.deny' => 1 ,
164 sub write_lxc_config
{
165 my ( $filename, $data ) = @_ ;
169 return $raw if ! $data ;
171 my $done_hash = { digest
=> 1 };
173 my $dump_entry = sub {
174 my ( $k, $elem, $done_hash, $snap ) = @_ ;
175 my $value = $elem ->{ $k };
176 return if ! defined ( $value );
177 return if $done_hash ->{ $k };
178 $done_hash ->{ $k } = 1 ;
180 die "got unexpected reference for ' $k '"
181 if ! $lxc_array_configs ->{ $k };
182 foreach my $v ( @$value ) {
183 $raw .= "snap\." if $snap ;
187 $raw .= "snap\." if $snap ;
188 $raw .= " $k = $value\n " ;
192 my $config_writer = sub {
193 my ( $elem, $snapshot ) = @_ ;
195 my $done_hash = { digest
=> 1 };
197 if ( $elem ->{ 'pve.snapname' }) {
198 & $dump_entry ( 'pve.snapname' , $elem, $done_hash, $snapshot );
201 # Note: Order is important! Include defaults first, so that we
202 # can overwrite them later.
203 & $dump_entry ( 'lxc.include' , $elem, $done_hash, $snapshot );
205 foreach my $k ( sort keys %$elem ) {
206 next if $k !~ m/^lxc\./ ;
207 & $dump_entry ( $k, $elem, $done_hash, $snapshot );
209 foreach my $k ( sort keys %$elem ) {
210 next if $k !~ m/^pve\./ ;
211 & $dump_entry ( $k, $elem, $done_hash, $snapshot );
213 my $network_count = 0 ;
215 foreach my $k ( sort keys %$elem ) {
216 next if $k !~ m/^net\d+$/ ;
217 $done_hash ->{ $k } = 1 ;
219 my $net = $elem ->{ $k };
221 $raw .= "snap\." if $snapshot ;
222 $raw .= "lxc.network.type = $net ->{type} \n " ;
223 foreach my $subkey ( sort keys %$net ) {
224 next if $subkey eq 'type' ;
225 if ( $valid_lxc_network_keys ->{ $subkey }) {
226 $raw .= "snap\." if $snapshot ;
227 $raw .= "lxc.network. $subkey = $net ->{ $subkey } \n " ;
228 } elsif ( $valid_pve_network_keys ->{ $subkey }) {
229 $raw .= "snap\." if $snapshot ;
230 $raw .= "pve.network. $subkey = $net ->{ $subkey } \n " ;
232 die "found invalid network key ' $subkey '" ;
236 if (! $network_count ) {
237 $raw .= "snap\." if $snapshot ;
238 $raw .= "lxc.network.type = empty \n " ;
240 foreach my $k ( sort keys %$elem ) {
241 next if $k eq 'snapshots' ;
242 next if $done_hash ->{ $k };
243 die "found un-written value in config - implement this!" ;
248 & $config_writer ( $data );
250 if ( $data ->{ snapshots
}){
251 my @tmp = sort { $data ->{ snapshots
}->{ $b }{ 'pve.snaptime' } <=>
252 $data ->{ snapshots
}->{ $a }{ 'pve.snaptime' } }
253 keys %{ $data ->{ snapshots
}};
254 foreach my $snapname ( @tmp ) {
256 & $config_writer ( $data ->{ snapshots
}->{ $snapname }, 1 );
263 sub parse_lxc_option
{
264 my ( $name, $value ) = @_ ;
266 my $parser = $valid_lxc_keys ->{ $name };
268 die "invalid key ' $name ' \n " if ! defined ( $parser );
270 if ( $parser eq '1' ) {
272 } elsif ( ref ( $parser )) {
273 my $res = & $parser ( $name, $value );
274 return $res if defined ( $res );
277 return $value if $value =~ m/^$parser$/ ;
280 die "unable to parse value ' $value ' for option ' $name ' \n " ;
283 sub parse_lxc_config
{
284 my ( $filename, $raw ) = @_ ;
286 return undef if ! defined ( $raw );
289 digest
=> Digest
:: SHA
:: sha1_hex
( $raw ),
292 $filename =~ m
| /lxc/ ( \d
+)/ config
$|
293 || die "got strange filename ' $filename '" ;
297 my $split_config = sub {
301 while ( $raw && $raw =~ s/^(.*)?(\n|$)// ) {
304 push (@{ $sections }, $tmp );
310 push (@{ $sections }, $tmp );
316 my $sec = & $split_config ( $raw );
318 foreach my $sec_raw (@{ $sec }){
319 next if $sec_raw eq '' ;
320 my $snapname = undef ;
322 my $network_counter = 0 ;
323 my $network_list = [];
324 my $host_ifnames = {};
326 my $find_next_hostif_name = sub {
327 for ( my $i = 0 ; $i < 10 ; $i++ ) {
328 my $name = "veth${vmid}. $i " ;
329 if (! $host_ifnames ->{ $name }) {
330 $host_ifnames ->{ $name } = 1 ;
335 die "unable to find free host_ifname" ; # should not happen
338 my $push_network = sub {
341 push @{ $network_list }, $netconf ;
343 if ( my $netname = $netconf ->{ 'veth.pair' }) {
344 if ( $netname =~ m/^veth(\d+).(\d)$/ ) {
345 die "wrong vmid for network interface pair \n " if $1 != $vmid ;
346 my $host_ifnames ->{ $netname } = 1 ;
348 die "wrong network interface pair \n " ;
355 while ( $sec_raw && $sec_raw =~ s/^(.*?)(\n|$)// ) {
358 next if $line =~ m/^\#/ ;
359 next if $line =~ m/^\s*$/ ;
361 if ( $line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
362 my ( $subkey, $value ) = ( $2, $3 );
363 if ( $subkey eq 'type' ) {
364 & $push_network ( $network );
365 $network = { type
=> $value };
366 } elsif ( $valid_lxc_network_keys ->{ $subkey }) {
367 $network ->{ $subkey } = $value ;
369 die "unable to parse config line: $line\n " ;
373 if ( $line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/ ) {
374 my ( $subkey, $value ) = ( $2, $3 );
375 if ( $valid_pve_network_keys ->{ $subkey }) {
376 $network ->{ $subkey } = $value ;
378 die "unable to parse config line: $line\n " ;
382 if ( $line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/ ) {
383 my ( $name, $value ) = ( $2, $3 );
385 $data ->{ snapshots
}->{ $snapname }->{ $name } = $value ;
389 if ( $line =~ m/^(snap\.)?pve\.snapname = (\w*)$/ ) {
392 $data ->{ snapshots
}->{ $snapname }->{ 'pve.snapname' } = $snapname ;
394 die "Configuarion broken \n " ;
398 if ( $line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/ ) {
399 my ( $name, $value ) = ( $2, $3 );
401 if ( $lxc_array_configs ->{ $name }) {
402 $data ->{ $name } = [] if ! defined ( $data ->{ $name });
404 push @{ $data ->{ snapshots
}->{ $snapname }->{ $name }}, parse_lxc_option
( $name, $value );
406 push @{ $data ->{ $name }}, parse_lxc_option
( $name, $value );
410 die "multiple definitions for $name\n " if defined ( $data ->{ snapshots
}->{ $snapname }->{ $name });
411 $data ->{ snapshots
}->{ $snapname }->{ $name } = parse_lxc_option
( $name, $value );
413 die "multiple definitions for $name\n " if defined ( $data ->{ $name });
414 $data ->{ $name } = parse_lxc_option
( $name, $value );
420 die "unable to parse config line: $line\n " ;
422 & $push_network ( $network );
424 foreach my $net (@{ $network_list }) {
425 next if $net ->{ type
} eq 'empty' ; # skip
426 $net ->{ 'veth.pair' } = & $find_next_hostif_name () if ! $net ->{ 'veth.pair' };
427 $net ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $net ->{ hwaddr
};
428 die "unsupported network type ' $net ->{type}' \n " if $net ->{ type
} ne 'veth' ;
430 if ( $net ->{ 'veth.pair' } =~ m/^veth\d+.(\d+)$/ ) {
432 $data ->{ snapshots
}->{ $snapname }->{ "net $1 " } = $net
434 $data ->{ "net $1 " } = $net ;
445 my $vmlist = PVE
:: Cluster
:: get_vmlist
();
447 return $res if ! $vmlist || ! $vmlist ->{ ids
};
448 my $ids = $vmlist ->{ ids
};
450 foreach my $vmid ( keys %$ids ) {
451 next if ! $vmid ; # skip CT0
452 my $d = $ids ->{ $vmid };
453 next if ! $d ->{ node
} || $d ->{ node
} ne $nodename ;
454 next if ! $d ->{ type
} || $d ->{ type
} ne 'lxc' ;
455 $res ->{ $vmid }->{ type
} = 'lxc' ;
460 sub cfs_config_path
{
461 my ( $vmid, $node ) = @_ ;
463 $node = $nodename if ! $node ;
464 return "nodes/ $node/lxc/$vmid/config " ;
468 my ( $vmid, $node ) = @_ ;
470 my $cfspath = cfs_config_path
( $vmid, $node );
471 return "/etc/pve/ $cfspath " ;
477 my $cfspath = cfs_config_path
( $vmid );
479 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath );
480 die "container $vmid does not exists \n " if ! defined ( $conf );
486 my ( $vmid, $conf ) = @_ ;
488 my $dir = "/etc/pve/nodes/ $nodename/lxc " ;
492 mkdir ( $dir ) || die "unable to create container configuration directory - $!\n " ;
494 write_config
( $vmid, $conf );
500 my $dir = "/etc/pve/nodes/ $nodename/lxc/$vmid " ;
501 File
:: Path
:: rmtree
( $dir );
505 my ( $vmid, $conf ) = @_ ;
507 my $cfspath = cfs_config_path
( $vmid );
509 PVE
:: Cluster
:: cfs_write_file
( $cfspath, $conf );
513 sub write_temp_config
{
514 my ( $vmid, $conf ) = @_ ;
517 my $filename = "/tmp/temp-lxc-conf- $vmid - $$ - $tempcounter .conf" ;
519 my $raw = write_lxc_config
( $filename, $conf );
521 PVE
:: Tools
:: file_set_contents
( $filename, $raw );
526 # flock: we use one file handle per process, so lock file
527 # can be called multiple times and succeeds for the same process.
529 my $lock_handles = {};
530 my $lockdir = "/run/lock/lxc" ;
535 return " $lockdir/pve -config-{ $vmid }.lock" ;
539 my ( $vmid, $timeout ) = @_ ;
541 $timeout = 10 if ! $timeout ;
544 my $filename = lock_filename
( $vmid );
546 mkdir $lockdir if !- d
$lockdir ;
548 my $lock_func = sub {
549 if (! $lock_handles ->{ $$ }->{ $filename }) {
550 my $fh = new IO
:: File
( ">> $filename " ) ||
551 die "can't open file - $!\n " ;
552 $lock_handles ->{ $$ }->{ $filename } = { fh
=> $fh, refcount
=> 0 };
555 if (! flock ( $lock_handles ->{ $$ }->{ $filename }->{ fh
}, $mode | LOCK_NB
)) {
556 print STDERR
"trying to aquire lock..." ;
559 $success = flock ( $lock_handles ->{ $$ }->{ $filename }->{ fh
}, $mode );
560 # try again on EINTR (see bug #273)
561 if ( $success || ( $! != EINTR
)) {
566 print STDERR
" failed \n " ;
567 die "can't aquire lock - $!\n " ;
570 $lock_handles ->{ $$ }->{ $filename }->{ refcount
}++;
572 print STDERR
" OK \n " ;
576 eval { PVE
:: Tools
:: run_with_timeout
( $timeout, $lock_func ); };
579 die "can't lock file ' $filename ' - $err " ;
586 my $filename = lock_filename
( $vmid );
588 if ( my $fh = $lock_handles ->{ $$ }->{ $filename }->{ fh
}) {
589 my $refcount = -- $lock_handles ->{ $$ }->{ $filename }->{ refcount
};
590 if ( $refcount <= 0 ) {
591 $lock_handles ->{ $$ }->{ $filename } = undef ;
598 my ( $vmid, $timeout, $code, @param ) = @_ ;
602 lock_aquire
( $vmid, $timeout );
603 eval { $res = & $code ( @param ) };
616 description
=> "Specifies whether a VM will be started during system bootup." ,
619 startup
=> get_standard_option
( 'pve-startup-order' ),
623 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." ,
631 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." ,
639 description
=> "Amount of RAM for the VM in MB." ,
646 description
=> "Amount of SWAP for the VM in MB." ,
653 description
=> "Amount of disk space for the VM in GB. A zero indicates no limits." ,
659 description
=> "Set a host name for the container." ,
666 description
=> "Container description. Only used on the configuration web interface." ,
671 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver." ,
676 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." ,
680 my $MAX_LXC_NETWORKS = 10 ;
681 for ( my $i = 0 ; $i < $MAX_LXC_NETWORKS ; $i++ ) {
682 $confdesc ->{ "net $i " } = {
684 type
=> 'string' , format
=> 'pve-lxc-network' ,
685 description
=> "Specifies network interfaces for the container. \n\n " .
686 "The string should have the follow format: \n\n " .
687 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>] \n " .
688 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>] \n " .
689 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>] \n " .
690 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]" ,
697 return defined ( $confdesc ->{ $name });
700 # add JSON properties for create and set function
701 sub json_config_properties
{
704 foreach my $opt ( keys %$confdesc ) {
705 $prop ->{ $opt } = $confdesc ->{ $opt };
711 # container status helpers
713 sub list_active_containers
{
715 my $filename = "/proc/net/unix" ;
717 # similar test is used by lcxcontainers.c: list_active_containers
720 my $fh = IO
:: File-
> new ( $filename, "r" );
723 while ( defined ( my $line = < $fh >)) {
724 if ( $line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/ ) {
726 if ( $path =~ m!^@/etc/pve/lxc/(\d+)/command$! ) {
737 # warning: this is slow
741 my $active_hash = list_active_containers
();
743 return 1 if defined ( $active_hash ->{ $vmid });
748 sub get_container_disk_usage
{
751 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '--' , 'df' , '-P' , '-B' , '1' , '/' ];
761 if ( my ( $fsid, $total, $used, $avail ) = $line =~
762 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/ ) {
770 eval { PVE
:: Tools
:: run_command
( $cmd, timeout
=> 1 , outfunc
=> $parser ); };
779 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
781 my $active_hash = list_active_containers
();
783 foreach my $vmid ( keys %$list ) {
784 my $d = $list ->{ $vmid };
786 my $running = defined ( $active_hash ->{ $vmid });
788 $d ->{ status
} = $running ?
'running' : 'stopped' ;
790 my $cfspath = cfs_config_path
( $vmid );
791 my $conf = PVE
:: Cluster
:: cfs_read_file
( $cfspath ) || {};
793 $d ->{ name
} = $conf ->{ 'lxc.utsname' } || "CT $vmid " ;
794 $d ->{ name
} =~ s/[\s]//g ;
798 my $cfs_period_us = $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
799 my $cfs_quota_us = $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
801 if ( $cfs_period_us && $cfs_quota_us ) {
802 $d ->{ cpus
} = int ( $cfs_quota_us/$cfs_period_us );
806 $d ->{ maxdisk
} = defined ( $conf ->{ 'pve.disksize' }) ?
807 int ( $conf ->{ 'pve.disksize' }* 1024 * 1024 )* 1024 : 1024 * 1024 * 1024 * 1024 * 1024 ;
809 if ( my $private = $conf ->{ 'lxc.rootfs' }) {
810 if ( $private =~ m!^/! ) {
811 my $res = PVE
:: Tools
:: df
( $private, 2 );
812 $d ->{ disk
} = $res ->{ used
};
813 $d ->{ maxdisk
} = $res ->{ total
};
815 if ( $private =~ m!^(?:loop|nbd):(?:\S+)$! ) {
816 my $res = get_container_disk_usage
( $vmid );
817 $d ->{ disk
} = $res ->{ used
};
818 $d ->{ maxdisk
} = $res ->{ total
};
825 $d ->{ maxmem
} = ( $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }|| 0 ) +
826 ( $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }|| 0 );
838 foreach my $vmid ( keys %$list ) {
839 my $d = $list ->{ $vmid };
840 next if $d ->{ status
} ne 'running' ;
842 $d ->{ uptime
} = 100 ; # fixme:
844 $d ->{ mem
} = read_cgroup_value
( 'memory' , $vmid, 'memory.usage_in_bytes' );
845 $d ->{ swap
} = read_cgroup_value
( 'memory' , $vmid, 'memory.memsw.usage_in_bytes' ) - $d ->{ mem
};
847 my $blkio_bytes = read_cgroup_value
( 'blkio' , $vmid, 'blkio.throttle.io_service_bytes' , 1 );
848 my @bytes = split ( /\n/ , $blkio_bytes );
849 foreach my $byte ( @bytes ) {
850 if ( my ( $key, $value ) = $byte =~ /(Read|Write)\s+(\d+)/ ) {
851 $d ->{ diskread
} = $2 if $key eq 'Read' ;
852 $d ->{ diskwrite
} = $2 if $key eq 'Write' ;
861 sub print_lxc_network
{
864 die "no network name defined \n " if ! $net ->{ name
};
866 my $res = "name= $net ->{name}" ;
868 foreach my $k ( qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag) ) {
869 next if ! defined ( $net ->{ $k });
870 $res .= ", $k = $net ->{ $k }" ;
876 sub parse_lxc_network
{
881 return $res if ! $data ;
883 foreach my $pv ( split ( /,/ , $data )) {
884 if ( $pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/ ) {
891 $res ->{ type
} = 'veth' ;
892 $res ->{ hwaddr
} = PVE
:: Tools
:: random_ether_addr
() if ! $res ->{ hwaddr
};
897 sub read_cgroup_value
{
898 my ( $group, $vmid, $name, $full ) = @_ ;
900 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
902 return PVE
:: Tools
:: file_get_contents
( $path ) if $full ;
904 return PVE
:: Tools
:: file_read_firstline
( $path );
907 sub write_cgroup_value
{
908 my ( $group, $vmid, $name, $value ) = @_ ;
910 my $path = "/sys/fs/cgroup/ $group/lxc/$vmid/$name " ;
911 PVE
:: ProcFSTools
:: write_proc_entry
( $path, $value ) if - e
$path ;
915 sub find_lxc_console_pids
{
919 PVE
:: Tools
:: dir_glob_foreach
( '/proc' , '\d+' , sub {
922 my $cmdline = PVE
:: Tools
:: file_read_firstline
( "/proc/ $pid/cmdline " );
925 my @args = split ( /\0/ , $cmdline );
927 # serach for lxc-console -n <vmid>
928 return if scalar ( @args ) != 3 ;
929 return if $args [ 1 ] ne '-n' ;
930 return if $args [ 2 ] !~ m/^\d+$/ ;
931 return if $args [ 0 ] !~ m
|^( /usr/ bin
/) ?lxc-console
$|;
935 push @{ $res ->{ $vmid }}, $pid ;
947 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/ ;
949 PVE
:: Tools
:: run_command
([ 'lxc-info' , '-n' , $vmid ], outfunc
=> $parser );
951 die "unable to get PID for CT $vmid (not running?) \n " if ! $pid ;
956 my $ipv4_reverse_mask = [
992 # Note: we cannot use Net:IP, because that only allows strict
994 sub parse_ipv4_cidr
{
995 my ( $cidr, $noerr ) = @_ ;
997 if ( $cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ( $2 > 7 ) && ( $2 < 32 )) {
998 return { address
=> $1, netmask
=> $ipv4_reverse_mask ->[ $2 ] };
1001 return undef if $noerr ;
1003 die "unable to parse ipv4 address/mask \n " ;
1009 die "VM is locked ( $conf ->{'pve.lock'}) \n " if $conf ->{ 'pve.lock' };
1012 sub lxc_conf_to_pve
{
1013 my ( $vmid, $lxc_conf ) = @_ ;
1015 my $properties = json_config_properties
();
1017 my $conf = { digest
=> $lxc_conf ->{ digest
} };
1019 foreach my $k ( keys %$properties ) {
1021 if ( $k eq 'description' ) {
1022 if ( my $raw = $lxc_conf ->{ 'pve.comment' }) {
1023 $conf ->{ $k } = PVE
:: Tools
:: decode_text
( $raw );
1025 } elsif ( $k eq 'onboot' ) {
1026 $conf ->{ $k } = $lxc_conf ->{ 'pve.onboot' } if $lxc_conf ->{ 'pve.onboot' };
1027 } elsif ( $k eq 'startup' ) {
1028 $conf ->{ $k } = $lxc_conf ->{ 'pve.startup' } if $lxc_conf ->{ 'pve.startup' };
1029 } elsif ( $k eq 'hostname' ) {
1030 $conf ->{ $k } = $lxc_conf ->{ 'lxc.utsname' } if $lxc_conf ->{ 'lxc.utsname' };
1031 } elsif ( $k eq 'nameserver' ) {
1032 $conf ->{ $k } = $lxc_conf ->{ 'pve.nameserver' } if $lxc_conf ->{ 'pve.nameserver' };
1033 } elsif ( $k eq 'searchdomain' ) {
1034 $conf ->{ $k } = $lxc_conf ->{ 'pve.searchdomain' } if $lxc_conf ->{ 'pve.searchdomain' };
1035 } elsif ( $k eq 'memory' ) {
1036 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' }) {
1037 $conf ->{ $k } = int ( $value / ( 1024 * 1024 ));
1039 } elsif ( $k eq 'swap' ) {
1040 if ( my $value = $lxc_conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' }) {
1041 my $mem = $lxc_conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } || 0 ;
1042 $conf ->{ $k } = int (( $value - $mem ) / ( 1024 * 1024 ));
1044 } elsif ( $k eq 'cpulimit' ) {
1045 my $cfs_period_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
1046 my $cfs_quota_us = $lxc_conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
1048 if ( $cfs_period_us && $cfs_quota_us ) {
1049 $conf ->{ $k } = $cfs_quota_us/$cfs_period_us ;
1053 } elsif ( $k eq 'cpuunits' ) {
1054 $conf ->{ $k } = $lxc_conf ->{ 'lxc.cgroup.cpu.shares' } || 1024 ;
1055 } elsif ( $k eq 'disk' ) {
1056 $conf ->{ $k } = defined ( $lxc_conf ->{ 'pve.disksize' }) ?
1057 $lxc_conf ->{ 'pve.disksize' } : 0 ;
1058 } elsif ( $k =~ m/^net\d$/ ) {
1059 my $net = $lxc_conf ->{ $k };
1061 $conf ->{ $k } = print_lxc_network
( $net );
1065 if ( my $parent = $lxc_conf ->{ 'pve.parent' }) {
1066 $conf ->{ parent
} = $lxc_conf ->{ 'pve.parent' };
1069 if ( my $parent = $lxc_conf ->{ 'pve.snapcomment' }) {
1070 $conf ->{ description
} = $lxc_conf ->{ 'pve.snapcomment' };
1073 if ( my $parent = $lxc_conf ->{ 'pve.snaptime' }) {
1074 $conf ->{ snaptime
} = $lxc_conf ->{ 'pve.snaptime' };
1080 # verify and cleanup nameserver list (replace \0 with ' ')
1081 sub verify_nameserver_list
{
1082 my ( $nameserver_list ) = @_ ;
1085 foreach my $server ( PVE
:: Tools
:: split_list
( $nameserver_list )) {
1086 PVE
:: JSONSchema
:: pve_verify_ip
( $server );
1087 push @list, $server ;
1090 return join ( ' ' , @list );
1093 sub verify_searchdomain_list
{
1094 my ( $searchdomain_list ) = @_ ;
1097 foreach my $server ( PVE
:: Tools
:: split_list
( $searchdomain_list )) {
1098 # todo: should we add checks for valid dns domains?
1099 push @list, $server ;
1102 return join ( ' ' , @list );
1105 sub update_lxc_config
{
1106 my ( $vmid, $conf, $running, $param, $delete ) = @_ ;
1112 my $pid = find_lxc_pid
( $vmid );
1113 $rootdir = "/proc/ $pid/root " ;
1116 if ( defined ( $delete )) {
1117 foreach my $opt ( @$delete ) {
1118 if ( $opt eq 'hostname' || $opt eq 'memory' ) {
1119 die "unable to delete required option ' $opt ' \n " ;
1120 } elsif ( $opt eq 'swap' ) {
1121 delete $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' };
1122 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , - 1 );
1123 } elsif ( $opt eq 'description' ) {
1124 delete $conf ->{ 'pve.comment' };
1125 } elsif ( $opt eq 'onboot' ) {
1126 delete $conf ->{ 'pve.onboot' };
1127 } elsif ( $opt eq 'startup' ) {
1128 delete $conf ->{ 'pve.startup' };
1129 } elsif ( $opt eq 'nameserver' ) {
1130 delete $conf ->{ 'pve.nameserver' };
1131 push @nohotplug, $opt ;
1133 } elsif ( $opt eq 'searchdomain' ) {
1134 delete $conf ->{ 'pve.searchdomain' };
1135 push @nohotplug, $opt ;
1137 } elsif ( $opt =~ m/^net(\d)$/ ) {
1138 delete $conf ->{ $opt };
1141 PVE
:: Network
:: veth_delete
( "veth${vmid}. $netid " );
1145 PVE
:: LXC
:: write_config
( $vmid, $conf ) if $running ;
1149 foreach my $opt ( keys %$param ) {
1150 my $value = $param ->{ $opt };
1151 if ( $opt eq 'hostname' ) {
1152 $conf ->{ 'lxc.utsname' } = $value ;
1153 } elsif ( $opt eq 'onboot' ) {
1154 $conf ->{ 'pve.onboot' } = $value ?
1 : 0 ;
1155 } elsif ( $opt eq 'startup' ) {
1156 $conf ->{ 'pve.startup' } = $value ;
1157 } elsif ( $opt eq 'nameserver' ) {
1158 my $list = verify_nameserver_list
( $value );
1159 $conf ->{ 'pve.nameserver' } = $list ;
1160 push @nohotplug, $opt ;
1162 } elsif ( $opt eq 'searchdomain' ) {
1163 my $list = verify_searchdomain_list
( $value );
1164 $conf ->{ 'pve.searchdomain' } = $list ;
1165 push @nohotplug, $opt ;
1167 } elsif ( $opt eq 'memory' ) {
1168 $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' } = $value*1024*1024 ;
1169 write_cgroup_value
( "memory" , $vmid, "memory.limit_in_bytes" , $value*1024*1024 );
1170 } elsif ( $opt eq 'swap' ) {
1171 my $mem = $conf ->{ 'lxc.cgroup.memory.limit_in_bytes' };
1172 $mem = $param ->{ memory
}* 1024 * 1024 if $param ->{ memory
};
1173 $conf ->{ 'lxc.cgroup.memory.memsw.limit_in_bytes' } = $mem + $value*1024*1024 ;
1174 write_cgroup_value
( "memory" , $vmid, "memory.memsw.limit_in_bytes" , $mem + $value*1024*1024 );
1176 } elsif ( $opt eq 'cpulimit' ) {
1178 my $cfs_period_us = 100000 ;
1179 $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' } = $cfs_period_us ;
1180 $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' } = $cfs_period_us*$value ;
1181 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , $cfs_period_us*$value );
1183 delete $conf ->{ 'lxc.cgroup.cpu.cfs_period_us' };
1184 delete $conf ->{ 'lxc.cgroup.cpu.cfs_quota_us' };
1185 write_cgroup_value
( "cpu" , $vmid, "cpu.cfs_quota_us" , - 1 );
1187 } elsif ( $opt eq 'cpuunits' ) {
1188 $conf ->{ 'lxc.cgroup.cpu.shares' } = $value ;
1189 write_cgroup_value
( "cpu" , $vmid, "cpu.shares" , $value );
1190 } elsif ( $opt eq 'description' ) {
1191 $conf ->{ 'pve.comment' } = PVE
:: Tools
:: encode_text
( $value );
1192 } elsif ( $opt eq 'disk' ) {
1193 $conf ->{ 'pve.disksize' } = $value ;
1194 push @nohotplug, $opt ;
1196 } elsif ( $opt =~ m/^net(\d+)$/ ) {
1198 my $net = PVE
:: LXC
:: parse_lxc_network
( $value );
1199 $net ->{ 'veth.pair' } = "veth${vmid}. $netid " ;
1201 $conf ->{ $opt } = $net ;
1203 update_net
( $vmid, $conf, $opt, $net, $netid, $rootdir );
1208 PVE
:: LXC
:: write_config
( $vmid, $conf ) if $running ;
1211 if ( $running && scalar ( @nohotplug )) {
1212 die "unable to modify " . join ( ',' , @nohotplug ) . " while container is running \n " ;
1216 sub get_primary_ips
{
1219 # return data from net0
1221 my $net = $conf ->{ net0
};
1222 return undef if ! $net ;
1224 my $ipv4 = $net ->{ ip
};
1225 $ipv4 =~ s!/\d+$!! if $ipv4 ;
1226 my $ipv6 = $net ->{ ip
};
1227 $ipv6 =~ s!/\d+$!! if $ipv6 ;
1229 return ( $ipv4, $ipv6 );
1232 sub destory_lxc_container
{
1233 my ( $storage_cfg, $vmid, $conf ) = @_ ;
1235 if ( my $volid = $conf ->{ 'pve.volid' }) {
1237 my ( $vtype, $name, $owner ) = PVE
:: Storage
:: parse_volname
( $storage_cfg, $volid );
1238 die "got strange volid (containe is not owner!) \n " if $vmid != $owner ;
1240 PVE
:: Storage
:: vdisk_free
( $storage_cfg, $volid );
1242 destroy_config
( $vmid );
1245 my $cmd = [ 'lxc-destroy' , '-n' , $vmid ];
1247 PVE
:: Tools
:: run_command
( $cmd );
1251 my $safe_num_ne = sub {
1254 return 0 if ! defined ( $a ) && ! defined ( $b );
1255 return 1 if ! defined ( $a );
1256 return 1 if ! defined ( $b );
1261 my $safe_string_ne = sub {
1264 return 0 if ! defined ( $a ) && ! defined ( $b );
1265 return 1 if ! defined ( $a );
1266 return 1 if ! defined ( $b );
1272 my ( $vmid, $conf, $opt, $newnet, $netid, $rootdir ) = @_ ;
1274 my $veth = $newnet ->{ 'veth.pair' };
1275 my $vethpeer = $veth . "p" ;
1276 my $eth = $newnet ->{ name
};
1278 if ( $conf ->{ $opt }) {
1279 if (& $safe_string_ne ( $conf ->{ $opt }->{ hwaddr
}, $newnet ->{ hwaddr
}) ||
1280 & $safe_string_ne ( $conf ->{ $opt }->{ name
}, $newnet ->{ name
})) {
1282 PVE
:: Network
:: veth_delete
( $veth );
1283 delete $conf ->{ $opt };
1284 PVE
:: LXC
:: write_config
( $vmid, $conf );
1286 hotplug_net
( $vmid, $conf, $opt, $newnet );
1288 } elsif (& $safe_string_ne ( $conf ->{ $opt }->{ bridge
}, $newnet ->{ bridge
}) ||
1289 & $safe_num_ne ( $conf ->{ $opt }->{ tag
}, $newnet ->{ tag
}) ||
1290 & $safe_num_ne ( $conf ->{ $opt }->{ firewall
}, $newnet ->{ firewall
})) {
1292 if ( $conf ->{ $opt }->{ bridge
}){
1293 PVE
:: Network
:: tap_unplug
( $veth );
1294 delete $conf ->{ $opt }->{ bridge
};
1295 delete $conf ->{ $opt }->{ tag
};
1296 delete $conf ->{ $opt }->{ firewall
};
1297 PVE
:: LXC
:: write_config
( $vmid, $conf );
1300 PVE
:: Network
:: tap_plug
( $veth, $newnet ->{ bridge
}, $newnet ->{ tag
}, $newnet ->{ firewall
});
1301 $conf ->{ $opt }->{ bridge
} = $newnet ->{ bridge
} if $newnet ->{ bridge
};
1302 $conf ->{ $opt }->{ tag
} = $newnet ->{ tag
} if $newnet ->{ tag
};
1303 $conf ->{ $opt }->{ firewall
} = $newnet ->{ firewall
} if $newnet ->{ firewall
};
1304 PVE
:: LXC
:: write_config
( $vmid, $conf );
1307 hotplug_net
( $vmid, $conf, $opt, $newnet );
1310 update_ipconfig
( $vmid, $conf, $opt, $eth, $newnet, $rootdir );
1314 my ( $vmid, $conf, $opt, $newnet ) = @_ ;
1316 my $veth = $newnet ->{ 'veth.pair' };
1317 my $vethpeer = $veth . "p" ;
1318 my $eth = $newnet ->{ name
};
1320 PVE
:: Network
:: veth_create
( $veth, $vethpeer, $newnet ->{ bridge
}, $newnet ->{ hwaddr
});
1321 PVE
:: Network
:: tap_plug
( $veth, $newnet ->{ bridge
}, $newnet ->{ tag
}, $newnet ->{ firewall
});
1323 # attach peer in container
1324 my $cmd = [ 'lxc-device' , '-n' , $vmid, 'add' , $vethpeer, " $eth " ];
1325 PVE
:: Tools
:: run_command
( $cmd );
1327 # link up peer in container
1328 $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , 'link' , 'set' , $eth , 'up' ];
1329 PVE
:: Tools
:: run_command
( $cmd );
1331 $conf ->{ $opt }->{ type
} = 'veth' ;
1332 $conf ->{ $opt }->{ bridge
} = $newnet ->{ bridge
} if $newnet ->{ bridge
};
1333 $conf ->{ $opt }->{ tag
} = $newnet ->{ tag
} if $newnet ->{ tag
};
1334 $conf ->{ $opt }->{ firewall
} = $newnet ->{ firewall
} if $newnet ->{ firewall
};
1335 $conf ->{ $opt }->{ hwaddr
} = $newnet ->{ hwaddr
} if $newnet ->{ hwaddr
};
1336 $conf ->{ $opt }->{ name
} = $newnet ->{ name
} if $newnet ->{ name
};
1337 $conf ->{ $opt }->{ 'veth.pair' } = $newnet ->{ 'veth.pair' } if $newnet ->{ 'veth.pair' };
1339 delete $conf ->{ $opt }->{ ip
} if $conf ->{ $opt }->{ ip
};
1340 delete $conf ->{ $opt }->{ ip6
} if $conf ->{ $opt }->{ ip6
};
1341 delete $conf ->{ $opt }->{ gw
} if $conf ->{ $opt }->{ gw
};
1342 delete $conf ->{ $opt }->{ gw6
} if $conf ->{ $opt }->{ gw6
};
1344 PVE
:: LXC
:: write_config
( $vmid, $conf );
1347 sub update_ipconfig
{
1348 my ( $vmid, $conf, $opt, $eth, $newnet, $rootdir ) = @_ ;
1350 my $lxc_setup = PVE
:: LXCSetup-
> new ( $conf, $rootdir );
1352 my $optdata = $conf ->{ $opt };
1356 my $cmd = [ 'lxc-attach' , '-n' , $vmid, '-s' , 'NETWORK' , '--' , '/sbin/ip' , @_ ];
1357 PVE
:: Tools
:: run_command
( $cmd );
1360 my $change_ip_config = sub {
1361 my ( $ipversion ) = @_ ;
1363 my $family_opt = "- $ipversion " ;
1364 my $suffix = $ipversion == 4 ?
'' : $ipversion ;
1365 my $gw = "gw $suffix " ;
1366 my $ip = "ip $suffix " ;
1368 my $change_ip = & $safe_string_ne ( $optdata ->{ $ip }, $newnet ->{ $ip });
1369 my $change_gw = & $safe_string_ne ( $optdata ->{ $gw }, $newnet ->{ $gw });
1371 return if ! $change_ip && ! $change_gw ;
1373 # step 1: add new IP, if this fails we cancel
1374 if ( $change_ip && $newnet ->{ $ip }) {
1375 eval { & $netcmd ( $family_opt, 'addr' , 'add' , $newnet ->{ $ip }, 'dev' , $eth ); };
1382 # step 2: replace gateway
1383 # If this fails we delete the added IP and cancel.
1384 # If it succeeds we save the config and delete the old IP, ignoring
1385 # errors. The config is then saved.
1386 # Note: 'ip route replace' can add
1388 if ( $newnet ->{ $gw }) {
1389 eval { & $netcmd ( $family_opt, 'route' , 'replace' , 'default' , 'via' , $newnet ->{ $gw }); };
1392 # the route was not replaced, the old IP is still available
1393 # rollback (delete new IP) and cancel
1395 eval { & $netcmd ( $family_opt, 'addr' , 'del' , $newnet ->{ $ip }, 'dev' , $eth ); };
1396 warn $@ if $@ ; # no need to die here
1401 eval { & $netcmd ( $family_opt, 'route' , 'del' , 'default' ); };
1402 # if the route was not deleted, the guest might have deleted it manually
1408 # from this point on we safe the configuration
1409 # step 3: delete old IP ignoring errors
1410 if ( $change_ip && $optdata ->{ $ip }) {
1411 eval { & $netcmd ( $family_opt, 'addr' , 'del' , $optdata ->{ $ip }, 'dev' , $eth ); };
1412 warn $@ if $@ ; # no need to die here
1415 foreach my $property ( $ip, $gw ) {
1416 if ( $newnet ->{ $property }) {
1417 $optdata ->{ $property } = $newnet ->{ $property };
1419 delete $optdata ->{ $property };
1422 PVE
:: LXC
:: write_config
( $vmid, $conf );
1423 $lxc_setup -> setup_network ( $conf );
1426 & $change_ip_config ( 4 );
1427 & $change_ip_config ( 6 );