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 my $nodename = PVE
::INotify
::nodename
();
22 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
24 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
25 sub verify_lxc_network
{
26 my ($value, $noerr) = @_;
28 return $value if parse_lxc_network
($value);
30 return undef if $noerr;
32 die "unable to parse network setting\n";
35 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
36 sub verify_ct_mountpoint
{
37 my ($value, $noerr) = @_;
39 return $value if parse_ct_mountpoint
($value);
41 return undef if $noerr;
43 die "unable to parse CT mountpoint options\n";
46 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
47 type
=> 'string', format
=> 'pve-ct-mountpoint',
48 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
49 description
=> "Use volume as container root.",
57 description
=> "Lock/unlock the VM.",
58 enum
=> [qw(migrate backup snapshot rollback)],
63 description
=> "Specifies whether a VM will be started during system bootup.",
66 startup
=> get_standard_option
('pve-startup-order'),
70 description
=> "Enable/disable Template.",
76 enum
=> ['amd64', 'i386'],
77 description
=> "OS architecture type.",
83 enum
=> ['debian', 'ubuntu', 'centos'],
84 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
89 description
=> "Attach a console device (/dev/console) to the container.",
95 description
=> "Specify the number of tty available to the container",
103 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.",
111 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\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
119 description
=> "Amount of RAM for the VM in MB.",
126 description
=> "Amount of SWAP for the VM in MB.",
132 description
=> "Set a host name for the container.",
139 description
=> "Container description. Only used on the configuration web interface.",
144 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
149 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.",
151 rootfs
=> get_standard_option
('pve-ct-rootfs'),
154 type
=> 'string', format
=> 'pve-configid',
156 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
160 description
=> "Timestamp for snapshots.",
166 my $valid_lxc_conf_keys = {
170 'lxc.haltsignal' => 1,
171 'lxc.rebootsignal' => 1,
172 'lxc.stopsignal' => 1,
174 'lxc.network.type' => 1,
175 'lxc.network.flags' => 1,
176 'lxc.network.link' => 1,
177 'lxc.network.mtu' => 1,
178 'lxc.network.name' => 1,
179 'lxc.network.hwaddr' => 1,
180 'lxc.network.ipv4' => 1,
181 'lxc.network.ipv4.gateway' => 1,
182 'lxc.network.ipv6' => 1,
183 'lxc.network.ipv6.gateway' => 1,
184 'lxc.network.script.up' => 1,
185 'lxc.network.script.down' => 1,
187 'lxc.console.logfile' => 1,
190 'lxc.devttydir' => 1,
191 'lxc.hook.autodev' => 1,
195 'lxc.mount.entry' => 1,
196 'lxc.mount.auto' => 1,
198 'lxc.rootfs.mount' => 1,
199 'lxc.rootfs.options' => 1,
203 'lxc.aa_profile' => 1,
204 'lxc.aa_allow_incomplete' => 1,
205 'lxc.se_context' => 1,
208 'lxc.hook.pre-start' => 1,
209 'lxc.hook.pre-mount' => 1,
210 'lxc.hook.mount' => 1,
211 'lxc.hook.start' => 1,
212 'lxc.hook.post-stop' => 1,
213 'lxc.hook.clone' => 1,
214 'lxc.hook.destroy' => 1,
217 'lxc.start.auto' => 1,
218 'lxc.start.delay' => 1,
219 'lxc.start.order' => 1,
221 'lxc.environment' => 1,
228 my $MAX_LXC_NETWORKS = 10;
229 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
230 $confdesc->{"net$i"} = {
232 type
=> 'string', format
=> 'pve-lxc-network',
233 description
=> "Specifies network interfaces for the container.\n\n".
234 "The string should have the follow format:\n\n".
235 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
236 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
237 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
238 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
242 my $MAX_MOUNT_POINTS = 10;
243 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
244 $confdesc->{"mp$i"} = {
246 type
=> 'string', format
=> 'pve-ct-mountpoint',
247 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
248 description
=> "Use volume as container mount point.",
253 sub write_pct_config
{
254 my ($filename, $conf) = @_;
256 delete $conf->{snapstate
}; # just to be sure
258 my $generate_raw_config = sub {
263 # add description as comment to top of file
264 my $descr = $conf->{description
} || '';
265 foreach my $cl (split(/\n/, $descr)) {
266 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
269 foreach my $key (sort keys %$conf) {
270 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
271 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
272 $raw .= "$key: $conf->{$key}\n";
275 if (my $lxcconf = $conf->{lxc
}) {
276 foreach my $entry (@$lxcconf) {
277 my ($k, $v) = @$entry;
285 my $raw = &$generate_raw_config($conf);
287 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
288 $raw .= "\n[$snapname]\n";
289 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
296 my ($key, $value) = @_;
298 die "unknown setting '$key'\n" if !$confdesc->{$key};
300 my $type = $confdesc->{$key}->{type
};
302 if (!defined($value)) {
303 die "got undefined value\n";
306 if ($value =~ m/[\n\r]/) {
307 die "property contains a line feed\n";
310 if ($type eq 'boolean') {
311 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
312 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
313 die "type check ('boolean') failed - got '$value'\n";
314 } elsif ($type eq 'integer') {
315 return int($1) if $value =~ m/^(\d+)$/;
316 die "type check ('integer') failed - got '$value'\n";
317 } elsif ($type eq 'number') {
318 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
319 die "type check ('number') failed - got '$value'\n";
320 } elsif ($type eq 'string') {
321 if (my $fmt = $confdesc->{$key}->{format
}) {
322 PVE
::JSONSchema
::check_format
($fmt, $value);
331 sub parse_pct_config
{
332 my ($filename, $raw) = @_;
334 return undef if !defined($raw);
337 digest
=> Digest
::SHA
::sha1_hex
($raw),
341 $filename =~ m
|/lxc/(\d
+).conf
$|
342 || die "got strange filename '$filename'";
350 my @lines = split(/\n/, $raw);
351 foreach my $line (@lines) {
352 next if $line =~ m/^\s*$/;
354 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
356 $conf->{description
} = $descr if $descr;
358 $conf = $res->{snapshots
}->{$section} = {};
362 if ($line =~ m/^\#(.*)\s*$/) {
363 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
367 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
370 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
371 push @{$conf->{lxc
}}, [$key, $value];
373 warn "vm $vmid - unable to parse config: $line\n";
375 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
376 $descr .= PVE
::Tools
::decode_text
($2);
377 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
378 $conf->{snapstate
} = $1;
379 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
382 eval { $value = check_type
($key, $value); };
383 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
384 $conf->{$key} = $value;
386 warn "vm $vmid - unable to parse config: $line\n";
390 $conf->{description
} = $descr if $descr;
392 delete $res->{snapstate
}; # just to be sure
398 my $vmlist = PVE
::Cluster
::get_vmlist
();
400 return $res if !$vmlist || !$vmlist->{ids
};
401 my $ids = $vmlist->{ids
};
403 foreach my $vmid (keys %$ids) {
404 next if !$vmid; # skip CT0
405 my $d = $ids->{$vmid};
406 next if !$d->{node
} || $d->{node
} ne $nodename;
407 next if !$d->{type
} || $d->{type
} ne 'lxc';
408 $res->{$vmid}->{type
} = 'lxc';
413 sub cfs_config_path
{
414 my ($vmid, $node) = @_;
416 $node = $nodename if !$node;
417 return "nodes/$node/lxc/$vmid.conf";
421 my ($vmid, $node) = @_;
423 my $cfspath = cfs_config_path
($vmid, $node);
424 return "/etc/pve/$cfspath";
430 my $cfspath = cfs_config_path
($vmid);
432 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
433 die "container $vmid does not exists\n" if !defined($conf);
439 my ($vmid, $conf) = @_;
441 my $dir = "/etc/pve/nodes/$nodename/lxc";
444 write_config
($vmid, $conf);
450 unlink config_file
($vmid, $nodename);
454 my ($vmid, $conf) = @_;
456 my $cfspath = cfs_config_path
($vmid);
458 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
461 # flock: we use one file handle per process, so lock file
462 # can be called multiple times and succeeds for the same process.
464 my $lock_handles = {};
465 my $lockdir = "/run/lock/lxc";
470 return "$lockdir/pve-config-{$vmid}.lock";
474 my ($vmid, $timeout) = @_;
476 $timeout = 10 if !$timeout;
479 my $filename = lock_filename
($vmid);
481 mkdir $lockdir if !-d
$lockdir;
483 my $lock_func = sub {
484 if (!$lock_handles->{$$}->{$filename}) {
485 my $fh = new IO
::File
(">>$filename") ||
486 die "can't open file - $!\n";
487 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
490 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
491 print STDERR
"trying to aquire lock...";
494 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
495 # try again on EINTR (see bug #273)
496 if ($success || ($! != EINTR
)) {
501 print STDERR
" failed\n";
502 die "can't aquire lock - $!\n";
505 $lock_handles->{$$}->{$filename}->{refcount
}++;
507 print STDERR
" OK\n";
511 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
514 die "can't lock file '$filename' - $err";
521 my $filename = lock_filename
($vmid);
523 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
524 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
525 if ($refcount <= 0) {
526 $lock_handles->{$$}->{$filename} = undef;
533 my ($vmid, $timeout, $code, @param) = @_;
537 lock_aquire
($vmid, $timeout);
538 eval { $res = &$code(@param) };
550 return defined($confdesc->{$name});
553 # add JSON properties for create and set function
554 sub json_config_properties
{
557 foreach my $opt (keys %$confdesc) {
558 next if $opt eq 'parent' || $opt eq 'snaptime';
559 next if $prop->{$opt};
560 $prop->{$opt} = $confdesc->{$opt};
566 sub json_config_properties_no_rootfs
{
569 foreach my $opt (keys %$confdesc) {
570 next if $prop->{$opt};
571 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
572 $prop->{$opt} = $confdesc->{$opt};
578 # container status helpers
580 sub list_active_containers
{
582 my $filename = "/proc/net/unix";
584 # similar test is used by lcxcontainers.c: list_active_containers
587 my $fh = IO
::File-
>new ($filename, "r");
590 while (defined(my $line = <$fh>)) {
591 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
593 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
604 # warning: this is slow
608 my $active_hash = list_active_containers
();
610 return 1 if defined($active_hash->{$vmid});
615 sub get_container_disk_usage
{
618 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
628 if (my ($fsid, $total, $used, $avail) = $line =~
629 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
637 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
646 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
648 my $active_hash = list_active_containers
();
650 foreach my $vmid (keys %$list) {
651 my $d = $list->{$vmid};
653 my $running = defined($active_hash->{$vmid});
655 $d->{status
} = $running ?
'running' : 'stopped';
657 my $cfspath = cfs_config_path
($vmid);
658 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
660 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
661 $d->{name
} =~ s/[\s]//g;
663 $d->{cpus
} = $conf->{cpulimit
} // 0;
666 my $res = get_container_disk_usage
($vmid);
667 $d->{disk
} = $res->{used
};
668 $d->{maxdisk
} = $res->{total
};
671 # use 4GB by default ??
672 if (my $rootfs = $conf->{rootfs
}) {
673 my $rootinfo = parse_ct_mountpoint
($rootfs);
674 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
676 $d->{maxdisk
} = 4*1024*1024*1024;
682 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
683 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
694 $d->{template
} = is_template
($conf);
697 foreach my $vmid (keys %$list) {
698 my $d = $list->{$vmid};
699 next if $d->{status
} ne 'running';
701 $d->{uptime
} = 100; # fixme:
703 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
704 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
706 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
707 my @bytes = split(/\n/, $blkio_bytes);
708 foreach my $byte (@bytes) {
709 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
710 $d->{diskread
} = $2 if $key eq 'Read';
711 $d->{diskwrite
} = $2 if $key eq 'Write';
719 my $parse_size = sub {
722 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
723 my ($size, $unit) = ($1, $3);
726 $size = $size * 1024;
727 } elsif ($unit eq 'M') {
728 $size = $size * 1024 * 1024;
729 } elsif ($unit eq 'G') {
730 $size = $size * 1024 * 1024 * 1024;
736 sub parse_ct_mountpoint
{
743 foreach my $p (split (/,/, $data)) {
744 next if $p =~ m/^\s*$/;
746 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
747 my ($k, $v) = ($1, $2);
748 return undef if defined($res->{$k});
751 if (!$res->{volume
} && $p !~ m/=/) {
759 return undef if !$res->{volume
};
761 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
764 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
770 sub print_ct_mountpoint
{
775 die "missing volume\n" if !$info->{volume
};
777 foreach my $o ('size', 'backup') {
778 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
781 return "$info->{volume}$opts";
784 sub print_lxc_network
{
787 die "no network name defined\n" if !$net->{name
};
789 my $res = "name=$net->{name}";
791 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
792 next if !defined($net->{$k});
793 $res .= ",$k=$net->{$k}";
799 sub parse_lxc_network
{
804 return $res if !$data;
806 foreach my $pv (split (/,/, $data)) {
807 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
814 $res->{type
} = 'veth';
815 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
820 sub read_cgroup_value
{
821 my ($group, $vmid, $name, $full) = @_;
823 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
825 return PVE
::Tools
::file_get_contents
($path) if $full;
827 return PVE
::Tools
::file_read_firstline
($path);
830 sub write_cgroup_value
{
831 my ($group, $vmid, $name, $value) = @_;
833 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
834 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
838 sub find_lxc_console_pids
{
842 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
845 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
848 my @args = split(/\0/, $cmdline);
850 # serach for lxc-console -n <vmid>
851 return if scalar(@args) != 3;
852 return if $args[1] ne '-n';
853 return if $args[2] !~ m/^\d+$/;
854 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
858 push @{$res->{$vmid}}, $pid;
870 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
872 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
874 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
879 my $ipv4_reverse_mask = [
915 # Note: we cannot use Net:IP, because that only allows strict
917 sub parse_ipv4_cidr
{
918 my ($cidr, $noerr) = @_;
920 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
921 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
924 return undef if $noerr;
926 die "unable to parse ipv4 address/mask\n";
932 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
935 sub update_lxc_config
{
936 my ($storage_cfg, $vmid, $conf) = @_;
938 my $dir = "/var/lib/lxc/$vmid";
940 if ($conf->{template
}) {
942 unlink "$dir/config";
949 die "missing 'arch' - internal error" if !$conf->{arch
};
950 $raw .= "lxc.arch = $conf->{arch}\n";
952 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
953 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
954 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
959 if (!has_dev_console
($conf)) {
960 $raw .= "lxc.console = none\n";
961 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
964 my $ttycount = get_tty_count
($conf);
965 $raw .= "lxc.tty = $ttycount\n";
967 my $utsname = $conf->{hostname
} || "CT$vmid";
968 $raw .= "lxc.utsname = $utsname\n";
970 my $memory = $conf->{memory
} || 512;
971 my $swap = $conf->{swap
} // 0;
973 my $lxcmem = int($memory*1024*1024);
974 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
976 my $lxcswap = int(($memory + $swap)*1024*1024);
977 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
979 if (my $cpulimit = $conf->{cpulimit
}) {
980 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
981 my $value = int(100000*$cpulimit);
982 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
985 my $shares = $conf->{cpuunits
} || 1024;
986 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
988 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
989 my $volid = $rootinfo->{volume
};
990 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
992 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
993 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
995 die "unable to use template as rootfs\n" if $isBase;
997 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
998 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1000 if ($format eq 'subvol') {
1001 $raw .= "lxc.rootfs = $path\n";
1002 } elsif ($format eq 'raw') {
1003 if ($scfg->{path
}) {
1004 $raw .= "lxc.rootfs = loop:$path\n";
1005 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1006 $raw .= "lxc.rootfs = $path\n";
1008 die "unsupported storage type '$scfg->{type}'\n";
1011 die "unsupported image format '$format'\n";
1015 foreach my $k (keys %$conf) {
1016 next if $k !~ m/^net(\d+)$/;
1018 my $d = parse_lxc_network
($conf->{$k});
1020 $raw .= "lxc.network.type = veth\n";
1021 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1022 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1023 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1024 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1027 if (my $lxcconf = $conf->{lxc
}) {
1028 foreach my $entry (@$lxcconf) {
1029 my ($k, $v) = @$entry;
1030 $netcount++ if $k eq 'lxc.network.type';
1031 $raw .= "$k = $v\n";
1035 $raw .= "lxc.network.type = empty\n" if !$netcount;
1037 File
::Path
::mkpath
("$dir/rootfs");
1039 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1042 # verify and cleanup nameserver list (replace \0 with ' ')
1043 sub verify_nameserver_list
{
1044 my ($nameserver_list) = @_;
1047 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1048 PVE
::JSONSchema
::pve_verify_ip
($server);
1049 push @list, $server;
1052 return join(' ', @list);
1055 sub verify_searchdomain_list
{
1056 my ($searchdomain_list) = @_;
1059 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1060 # todo: should we add checks for valid dns domains?
1061 push @list, $server;
1064 return join(' ', @list);
1067 sub update_pct_config
{
1068 my ($vmid, $conf, $running, $param, $delete) = @_;
1074 my $pid = find_lxc_pid
($vmid);
1075 $rootdir = "/proc/$pid/root";
1078 if (defined($delete)) {
1079 foreach my $opt (@$delete) {
1080 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1081 die "unable to delete required option '$opt'\n";
1082 } elsif ($opt eq 'swap') {
1083 delete $conf->{$opt};
1084 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1085 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1086 delete $conf->{$opt};
1087 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1088 $opt eq 'tty' || $opt eq 'console') {
1089 delete $conf->{$opt};
1090 push @nohotplug, $opt;
1092 } elsif ($opt =~ m/^net(\d)$/) {
1093 delete $conf->{$opt};
1096 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1100 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1104 # There's no separate swap size to configure, there's memory and "total"
1105 # memory (iow. memory+swap). This means we have to change them together.
1106 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1107 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1108 if (defined($wanted_memory) || defined($wanted_swap)) {
1110 $wanted_memory //= ($conf->{memory
} || 512);
1111 $wanted_swap //= ($conf->{swap
} || 0);
1113 my $total = $wanted_memory + $wanted_swap;
1115 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1116 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1118 $conf->{memory
} = $wanted_memory;
1119 $conf->{swap
} = $wanted_swap;
1121 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1124 foreach my $opt (keys %$param) {
1125 my $value = $param->{$opt};
1126 if ($opt eq 'hostname') {
1127 $conf->{$opt} = $value;
1128 } elsif ($opt eq 'onboot') {
1129 $conf->{$opt} = $value ?
1 : 0;
1130 } elsif ($opt eq 'startup') {
1131 $conf->{$opt} = $value;
1132 } elsif ($opt eq 'tty' || $opt eq 'console') {
1133 $conf->{$opt} = $value;
1134 push @nohotplug, $opt;
1136 } elsif ($opt eq 'nameserver') {
1137 my $list = verify_nameserver_list
($value);
1138 $conf->{$opt} = $list;
1139 push @nohotplug, $opt;
1141 } elsif ($opt eq 'searchdomain') {
1142 my $list = verify_searchdomain_list
($value);
1143 $conf->{$opt} = $list;
1144 push @nohotplug, $opt;
1146 } elsif ($opt eq 'cpulimit') {
1147 $conf->{$opt} = $value;
1148 push @nohotplug, $opt; # fixme: hotplug
1150 } elsif ($opt eq 'cpuunits') {
1151 $conf->{$opt} = $value;
1152 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1153 } elsif ($opt eq 'description') {
1154 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1155 } elsif ($opt =~ m/^net(\d+)$/) {
1157 my $net = parse_lxc_network
($value);
1159 $conf->{$opt} = print_lxc_network
($net);
1161 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1164 die "implement me: $opt";
1166 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1169 if ($running && scalar(@nohotplug)) {
1170 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1174 sub has_dev_console
{
1177 return !(defined($conf->{console
}) && !$conf->{console
});
1183 return $conf->{tty
} // $confdesc->{tty
}->{default};
1186 sub get_primary_ips
{
1189 # return data from net0
1191 return undef if !defined($conf->{net0
});
1192 my $net = parse_lxc_network
($conf->{net0
});
1194 my $ipv4 = $net->{ip
};
1196 if ($ipv4 =~ /^(dhcp|manual)$/) {
1202 my $ipv6 = $net->{ip6
};
1204 if ($ipv6 =~ /^(dhcp|manual)$/) {
1211 return ($ipv4, $ipv6);
1215 sub destroy_lxc_container
{
1216 my ($storage_cfg, $vmid, $conf) = @_;
1218 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1219 if (defined($rootinfo->{volume
})) {
1220 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1221 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1223 rmdir "/var/lib/lxc/$vmid/rootfs";
1224 unlink "/var/lib/lxc/$vmid/config";
1225 rmdir "/var/lib/lxc/$vmid";
1226 destroy_config
($vmid);
1228 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1229 #PVE::Tools::run_command($cmd);
1232 sub vm_stop_cleanup
{
1233 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1237 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1238 PVE
::Storage
::deactivate_volumes
($storeage_cfg, [$rootinfo->{volume
}]);
1241 warn $@ if $@; # avoid errors - just warn
1244 my $safe_num_ne = sub {
1247 return 0 if !defined($a) && !defined($b);
1248 return 1 if !defined($a);
1249 return 1 if !defined($b);
1254 my $safe_string_ne = sub {
1257 return 0 if !defined($a) && !defined($b);
1258 return 1 if !defined($a);
1259 return 1 if !defined($b);
1265 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1267 if ($newnet->{type
} ne 'veth') {
1268 # for when there are physical interfaces
1269 die "cannot update interface of type $newnet->{type}";
1272 my $veth = "veth${vmid}i${netid}";
1273 my $eth = $newnet->{name
};
1275 if (my $oldnetcfg = $conf->{$opt}) {
1276 my $oldnet = parse_lxc_network
($oldnetcfg);
1278 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1279 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1281 PVE
::Network
::veth_delete
($veth);
1282 delete $conf->{$opt};
1283 PVE
::LXC
::write_config
($vmid, $conf);
1285 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1287 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1288 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1289 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1291 if ($oldnet->{bridge
}) {
1292 PVE
::Network
::tap_unplug
($veth);
1293 foreach (qw(bridge tag firewall)) {
1294 delete $oldnet->{$_};
1296 $conf->{$opt} = print_lxc_network
($oldnet);
1297 PVE
::LXC
::write_config
($vmid, $conf);
1300 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1301 foreach (qw(bridge tag firewall)) {
1302 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1304 $conf->{$opt} = print_lxc_network
($oldnet);
1305 PVE
::LXC
::write_config
($vmid, $conf);
1308 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1311 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1315 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1317 my $veth = "veth${vmid}i${netid}";
1318 my $vethpeer = $veth . "p";
1319 my $eth = $newnet->{name
};
1321 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1322 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1324 # attach peer in container
1325 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1326 PVE
::Tools
::run_command
($cmd);
1328 # link up peer in container
1329 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1330 PVE
::Tools
::run_command
($cmd);
1332 my $done = { type
=> 'veth' };
1333 foreach (qw(bridge tag firewall hwaddr name)) {
1334 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1336 $conf->{$opt} = print_lxc_network
($done);
1338 PVE
::LXC
::write_config
($vmid, $conf);
1341 sub update_ipconfig
{
1342 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1344 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1346 my $optdata = parse_lxc_network
($conf->{$opt});
1350 my $cmdargs = shift;
1351 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1353 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1355 my $change_ip_config = sub {
1356 my ($ipversion) = @_;
1358 my $family_opt = "-$ipversion";
1359 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1360 my $gw= "gw$suffix";
1361 my $ip= "ip$suffix";
1363 my $newip = $newnet->{$ip};
1364 my $newgw = $newnet->{$gw};
1365 my $oldip = $optdata->{$ip};
1367 my $change_ip = &$safe_string_ne($oldip, $newip);
1368 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1370 return if !$change_ip && !$change_gw;
1372 # step 1: add new IP, if this fails we cancel
1373 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1374 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1381 # step 2: replace gateway
1382 # If this fails we delete the added IP and cancel.
1383 # If it succeeds we save the config and delete the old IP, ignoring
1384 # errors. The config is then saved.
1385 # Note: 'ip route replace' can add
1388 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1391 # the route was not replaced, the old IP is still available
1392 # rollback (delete new IP) and cancel
1394 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1395 warn $@ if $@; # no need to die here
1400 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1401 # if the route was not deleted, the guest might have deleted it manually
1407 # from this point on we save the configuration
1408 # step 3: delete old IP ignoring errors
1409 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1410 # We need to enable promote_secondaries, otherwise our newly added
1411 # address will be removed along with the old one.
1414 if ($ipversion == 4) {
1415 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1416 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1417 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1419 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1421 warn $@ if $@; # no need to die here
1423 if ($ipversion == 4) {
1424 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1428 foreach my $property ($ip, $gw) {
1429 if ($newnet->{$property}) {
1430 $optdata->{$property} = $newnet->{$property};
1432 delete $optdata->{$property};
1435 $conf->{$opt} = print_lxc_network
($optdata);
1436 PVE
::LXC
::write_config
($vmid, $conf);
1437 $lxc_setup->setup_network($conf);
1440 &$change_ip_config(4);
1441 &$change_ip_config(6);
1445 # Internal snapshots
1447 # NOTE: Snapshot create/delete involves several non-atomic
1448 # action, and can take a long time.
1449 # So we try to avoid locking the file and use 'lock' variable
1450 # inside the config file instead.
1452 my $snapshot_copy_config = sub {
1453 my ($source, $dest) = @_;
1455 foreach my $k (keys %$source) {
1456 next if $k eq 'snapshots';
1457 next if $k eq 'snapstate';
1458 next if $k eq 'snaptime';
1459 next if $k eq 'vmstate';
1460 next if $k eq 'lock';
1461 next if $k eq 'digest';
1462 next if $k eq 'description';
1464 $dest->{$k} = $source->{$k};
1468 my $snapshot_prepare = sub {
1469 my ($vmid, $snapname, $comment) = @_;
1473 my $updatefn = sub {
1475 my $conf = load_config
($vmid);
1477 die "you can't take a snapshot if it's a template\n"
1478 if is_template
($conf);
1482 $conf->{lock} = 'snapshot';
1484 die "snapshot name '$snapname' already used\n"
1485 if defined($conf->{snapshots
}->{$snapname});
1487 my $storecfg = PVE
::Storage
::config
();
1488 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1490 $snap = $conf->{snapshots
}->{$snapname} = {};
1492 &$snapshot_copy_config($conf, $snap);
1494 $snap->{'snapstate'} = "prepare";
1495 $snap->{'snaptime'} = time();
1496 $snap->{'description'} = $comment if $comment;
1497 $conf->{snapshots
}->{$snapname} = $snap;
1499 PVE
::LXC
::write_config
($vmid, $conf);
1502 lock_container
($vmid, 10, $updatefn);
1507 my $snapshot_commit = sub {
1508 my ($vmid, $snapname) = @_;
1510 my $updatefn = sub {
1512 my $conf = load_config
($vmid);
1514 die "missing snapshot lock\n"
1515 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1517 die "snapshot '$snapname' does not exist\n"
1518 if !defined($conf->{snapshots
}->{$snapname});
1520 die "wrong snapshot state\n"
1521 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1522 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1524 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1525 delete $conf->{lock};
1526 $conf->{parent
} = $snapname;
1528 PVE
::LXC
::write_config
($vmid, $conf);
1531 lock_container
($vmid, 10 ,$updatefn);
1535 my ($feature, $conf, $storecfg, $snapname) = @_;
1537 #Fixme add other drives if necessary.
1540 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1541 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1543 return $err ?
0 : 1;
1546 sub snapshot_create
{
1547 my ($vmid, $snapname, $comment) = @_;
1549 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1551 my $conf = load_config
($vmid);
1553 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1554 my $running = check_running
($vmid);
1557 PVE
::Tools
::run_command
($cmd);
1560 my $storecfg = PVE
::Storage
::config
();
1561 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1562 my $volid = $rootinfo->{volume
};
1564 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1566 PVE
::Tools
::run_command
($cmd);
1569 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1570 &$snapshot_commit($vmid, $snapname);
1573 snapshot_delete
($vmid, $snapname, 1);
1578 sub snapshot_delete
{
1579 my ($vmid, $snapname, $force) = @_;
1585 my $updatefn = sub {
1587 $conf = load_config
($vmid);
1589 die "you can't delete a snapshot if vm is a template\n"
1590 if is_template
($conf);
1592 $snap = $conf->{snapshots
}->{$snapname};
1596 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1598 $snap->{snapstate
} = 'delete';
1600 PVE
::LXC
::write_config
($vmid, $conf);
1603 lock_container
($vmid, 10, $updatefn);
1605 my $storecfg = PVE
::Storage
::config
();
1607 my $del_snap = sub {
1611 if ($conf->{parent
} eq $snapname) {
1612 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1613 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1615 delete $conf->{parent
};
1619 delete $conf->{snapshots
}->{$snapname};
1621 PVE
::LXC
::write_config
($vmid, $conf);
1624 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1625 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1626 my $volid = $rootinfo->{volume
};
1629 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1633 if(!$err || ($err && $force)) {
1634 lock_container
($vmid, 10, $del_snap);
1636 die "Can't delete snapshot: $vmid $snapname $err\n";
1641 sub snapshot_rollback
{
1642 my ($vmid, $snapname) = @_;
1644 my $storecfg = PVE
::Storage
::config
();
1646 my $conf = load_config
($vmid);
1648 die "you can't rollback if vm is a template\n" if is_template
($conf);
1650 my $snap = $conf->{snapshots
}->{$snapname};
1652 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1654 my $rootfs = $snap->{rootfs
};
1655 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1656 my $volid = $rootinfo->{volume
};
1658 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1660 my $updatefn = sub {
1662 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1663 if $snap->{snapstate
};
1667 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1669 die "unable to rollback vm $vmid: vm is running\n"
1670 if check_running
($vmid);
1672 $conf->{lock} = 'rollback';
1676 # copy snapshot config to current config
1678 my $tmp_conf = $conf;
1679 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1680 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1681 delete $conf->{snaptime
};
1682 delete $conf->{snapname
};
1683 $conf->{parent
} = $snapname;
1685 PVE
::LXC
::write_config
($vmid, $conf);
1688 my $unlockfn = sub {
1689 delete $conf->{lock};
1690 PVE
::LXC
::write_config
($vmid, $conf);
1693 lock_container
($vmid, 10, $updatefn);
1695 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1697 lock_container
($vmid, 5, $unlockfn);
1700 sub template_create
{
1701 my ($vmid, $conf) = @_;
1703 my $storecfg = PVE
::Storage
::config
();
1705 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1706 my $volid = $rootinfo->{volume
};
1708 die "Template feature is not available for '$volid'\n"
1709 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1711 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1713 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1714 $rootinfo->{volume
} = $template_volid;
1715 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1717 write_config
($vmid, $conf);
1723 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1726 sub foreach_mountpoint
{
1727 my ($conf, $func) = @_;
1729 foreach my $ms (keys %$conf) {
1730 next if $ms !~ m/^mp(\d+)/ && $ms ne 'rootfs';
1732 my $mountpoint = parse_ct_mountpoint
($conf->{$ms});
1733 next if !$mountpoint;
1735 &$func($ms, $mountpoint);