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 $dump_entry = sub {
172 my ($k, $value, $done_hash, $snapshot) = @_;
173 return if !defined($value);
174 return if $done_hash->{$k};
175 $done_hash->{$k} = 1;
177 die "got unexpected reference for '$k'"
178 if !$lxc_array_configs->{$k};
179 foreach my $v (@$value) {
180 $raw .= 'snap.' if $snapshot;
184 $raw .= 'snap.' if $snapshot;
185 $raw .= "$k = $value\n";
189 my $config_writer = sub {
190 my ($elem, $snapshot) = @_;
192 my $done_hash = { digest
=> 1};
194 if (defined(my $value = $elem->{'pve.snapname'})) {
195 &$dump_entry('pve.snapname', $value, $done_hash, $snapshot);
198 # Note: Order is important! Include defaults first, so that we
199 # can overwrite them later.
200 &$dump_entry('lxc.include', $elem->{'lxc.include'}, $done_hash, $snapshot);
202 foreach my $k (sort keys %$elem) {
203 next if $k !~ m/^lxc\./;
204 &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
206 foreach my $k (sort keys %$elem) {
207 next if $k !~ m/^pve\./;
208 &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
210 my $network_count = 0;
212 foreach my $k (sort keys %$elem) {
213 next if $k !~ m/^net\d+$/;
214 $done_hash->{$k} = 1;
216 my $net = $elem->{$k};
218 $raw .= 'snap.' if $snapshot;
219 $raw .= "lxc.network.type = $net->{type}\n";
220 foreach my $subkey (sort keys %$net) {
221 next if $subkey eq 'type';
222 if ($valid_lxc_network_keys->{$subkey}) {
223 $raw .= 'snap.' if $snapshot;
224 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
225 } elsif ($valid_pve_network_keys->{$subkey}) {
226 $raw .= 'snap.' if $snapshot;
227 $raw .= "pve.network.$subkey = $net->{$subkey}\n";
229 die "found invalid network key '$subkey'";
233 if (!$network_count) {
234 $raw .= 'snap.' if $snapshot;
235 $raw .= "lxc.network.type = empty\n";
237 foreach my $k (sort keys %$elem) {
238 next if $k eq 'snapshots';
239 next if $done_hash->{$k};
240 die "found un-written value \"$k\" in config
- implement this
!";
245 &$config_writer($data);
247 if ($data->{snapshots}) {
248 my @tmp = sort { $data->{snapshots}->{$b}{'pve.snaptime'} <=>
249 $data->{snapshots}->{$a}{'pve.snaptime'} }
250 keys %{$data->{snapshots}};
251 foreach my $snapname (@tmp) {
253 &$config_writer($data->{snapshots}->{$snapname}, 1);
260 sub parse_lxc_option {
261 my ($name, $value) = @_;
263 my $parser = $valid_lxc_keys->{$name};
265 die "invalid key
'$name'\n" if !defined($parser);
267 if ($parser eq '1') {
269 } elsif (ref($parser)) {
270 my $res = &$parser($name, $value);
271 return $res if defined($res);
274 return $value if $value =~ m/^$parser$/;
277 die "unable to parse value
'$value' for option
'$name'\n";
280 sub parse_lxc_config {
281 my ($filename, $raw) = @_;
283 return undef if !defined($raw);
286 digest => Digest::SHA::sha1_hex($raw),
289 $filename =~ m|/lxc/(\d+)/config$|
290 || die "got strange filename
'$filename'";
294 my $split_config = sub {
298 while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
301 push(@{$sections},$tmp);
307 push(@{$sections},$tmp);
312 my $sec = &$split_config($raw);
314 foreach my $sec_raw (@{$sec}){
315 next if $sec_raw eq '';
316 my $snapname = undef;
318 my $network_counter = 0;
319 my $network_list = [];
320 my $host_ifnames = {};
322 my $find_next_hostif_name = sub {
323 for (my $i = 0; $i < 10; $i++) {
324 my $name = "veth
${vmid
}.$i";
325 if (!$host_ifnames->{$name}) {
326 $host_ifnames->{$name} = 1;
331 die "unable to find free host_ifname
"; # should not happen
334 my $push_network = sub {
337 push @{$network_list}, $netconf;
339 if (my $netname = $netconf->{'veth.pair'}) {
340 if ($netname =~ m/^veth(\d+).(\d)$/) {
341 die "wrong vmid
for network interface pair
\n" if $1 != $vmid;
342 my $host_ifnames->{$netname} = 1;
344 die "wrong network interface pair
\n";
351 while ($sec_raw && $sec_raw =~ s/^(.*?)(\n|$)//) {
354 next if $line =~ m/^\#/;
355 next if $line =~ m/^\s*$/;
357 if ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
358 my ($subkey, $value) = ($2, $3);
359 if ($subkey eq 'type') {
360 &$push_network($network);
361 $network = { type => $value };
362 } elsif ($valid_lxc_network_keys->{$subkey}) {
363 $network->{$subkey} = $value;
365 die "unable to parse config line
: $line\n";
369 if ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
370 my ($subkey, $value) = ($2, $3);
371 if ($valid_pve_network_keys->{$subkey}) {
372 $network->{$subkey} = $value;
374 die "unable to parse config line
: $line\n";
378 if ($line =~ m/^(snap\.)?(pve.snapcomment)\s*=\s*(\S.*)\s*$/) {
379 my ($name, $value) = ($2, $3);
381 $data->{snapshots}->{$snapname}->{$name} = $value;
385 if ($line =~ m/^(snap\.)?pve\.snapname = (\w*)$/) {
388 $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
390 die "Configuarion broken
\n";
394 if ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
395 my ($name, $value) = ($2, $3);
397 if ($lxc_array_configs->{$name}) {
398 $data->{$name} = [] if !defined($data->{$name});
400 push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value);
402 push @{$data->{$name}}, parse_lxc_option($name, $value);
406 die "multiple definitions
for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
407 $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
409 die "multiple definitions
for $name\n" if defined($data->{$name});
410 $data->{$name} = parse_lxc_option($name, $value);
416 die "unable to parse config line
: $line\n";
418 &$push_network($network);
420 foreach my $net (@{$network_list}) {
421 next if $net->{type} eq 'empty'; # skip
422 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
423 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
424 die "unsupported network type
'$net->{type}'\n" if $net->{type} ne 'veth';
426 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
428 $data->{snapshots}->{$snapname}->{"net
$1"} = $net;
430 $data->{"net
$1"} = $net;
440 my $vmlist = PVE::Cluster::get_vmlist();
442 return $res if !$vmlist || !$vmlist->{ids};
443 my $ids = $vmlist->{ids};
445 foreach my $vmid (keys %$ids) {
446 next if !$vmid; # skip CT0
447 my $d = $ids->{$vmid};
448 next if !$d->{node} || $d->{node} ne $nodename;
449 next if !$d->{type} || $d->{type} ne 'lxc';
450 $res->{$vmid}->{type} = 'lxc';
455 sub cfs_config_path {
456 my ($vmid, $node) = @_;
458 $node = $nodename if !$node;
459 return "nodes
/$node/lxc/$vmid/config";
463 my ($vmid, $node) = @_;
465 my $cfspath = cfs_config_path($vmid, $node);
466 return "/etc/pve
/$cfspath";
472 my $cfspath = cfs_config_path($vmid);
474 my $conf = PVE::Cluster::cfs_read_file($cfspath);
475 die "container
$vmid does not exists\n" if !defined($conf);
481 my ($vmid, $conf) = @_;
483 my $dir = "/etc/pve
/nodes/$nodename/lxc";
487 mkdir($dir) || die "unable to create container configuration directory
- $!\n";
489 write_config($vmid, $conf);
495 my $dir = "/etc/pve
/nodes/$nodename/lxc/$vmid";
496 File::Path::rmtree($dir);
500 my ($vmid, $conf) = @_;
502 my $cfspath = cfs_config_path($vmid);
504 PVE::Cluster::cfs_write_file($cfspath, $conf);
508 sub write_temp_config {
509 my ($vmid, $conf) = @_;
512 my $filename = "/tmp/temp-lxc-conf-
$vmid-$$-$tempcounter.conf
";
514 my $raw = write_lxc_config($filename, $conf);
516 PVE::Tools::file_set_contents($filename, $raw);
521 # flock: we use one file handle per process, so lock file
522 # can be called multiple times and succeeds for the same process.
524 my $lock_handles = {};
525 my $lockdir = "/run/lock
/lxc
";
530 return "$lockdir/pve-config
-{$vmid}.lock";
534 my ($vmid, $timeout) = @_;
536 $timeout = 10 if !$timeout;
539 my $filename = lock_filename($vmid);
541 mkdir $lockdir if !-d $lockdir;
543 my $lock_func = sub {
544 if (!$lock_handles->{$$}->{$filename}) {
545 my $fh = new IO::File(">>$filename") ||
546 die "can
't open file - $!\n";
547 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
550 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
551 print STDERR "trying to aquire lock...";
554 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
555 # try again on EINTR (see bug #273)
556 if ($success || ($! != EINTR)) {
561 print STDERR " failed\n";
562 die "can't aquire
lock - $!\n";
565 $lock_handles->{$$}->{$filename}->{refcount}++;
567 print STDERR " OK
\n";
571 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
574 die "can
't lock file '$filename' - $err";
581 my $filename = lock_filename($vmid);
583 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
584 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
585 if ($refcount <= 0) {
586 $lock_handles->{$$}->{$filename} = undef;
593 my ($vmid, $timeout, $code, @param) = @_;
597 lock_aquire($vmid, $timeout);
598 eval { $res = &$code(@param) };
611 description => "Specifies whether a VM will be started during system bootup.",
614 startup => get_standard_option('pve-startup-order
'),
618 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.",
626 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.",
634 description => "Amount of RAM for the VM in MB.",
641 description => "Amount of SWAP for the VM in MB.",
648 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
654 description => "Set a host name for the container.",
661 description => "Container description. Only used on the configuration web interface.",
666 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
671 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.",
675 my $MAX_LXC_NETWORKS = 10;
676 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
677 $confdesc->{"net$i"} = {
679 type => 'string
', format => 'pve-lxc-network
',
680 description => "Specifies network interfaces for the container.\n\n".
681 "The string should have the follow format:\n\n".
682 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
683 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
684 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
685 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
692 return defined($confdesc->{$name});
695 # add JSON properties for create and set function
696 sub json_config_properties {
699 foreach my $opt (keys %$confdesc) {
700 $prop->{$opt} = $confdesc->{$opt};
706 # container status helpers
708 sub list_active_containers {
710 my $filename = "/proc/net/unix";
712 # similar test is used by lcxcontainers.c: list_active_containers
715 my $fh = IO::File->new ($filename, "r");
718 while (defined(my $line = <$fh>)) {
719 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
721 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
732 # warning: this is slow
736 my $active_hash = list_active_containers();
738 return 1 if defined($active_hash->{$vmid});
743 sub get_container_disk_usage {
746 my $cmd = ['lxc-attach
', '-n
', $vmid, '--', 'df
', '-P
', '-B
', '1', '/'];
756 if (my ($fsid, $total, $used, $avail) = $line =~
757 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
765 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
774 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc
' }} : config_list();
776 my $active_hash = list_active_containers();
778 foreach my $vmid (keys %$list) {
779 my $d = $list->{$vmid};
781 my $running = defined($active_hash->{$vmid});
783 $d->{status} = $running ? 'running
' : 'stopped
';
785 my $cfspath = cfs_config_path($vmid);
786 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
788 $d->{name} = $conf->{'lxc
.utsname
'} || "CT$vmid";
789 $d->{name} =~ s/[\s]//g;
793 my $cfs_period_us = $conf->{'lxc
.cgroup
.cpu
.cfs_period_us
'};
794 my $cfs_quota_us = $conf->{'lxc
.cgroup
.cpu
.cfs_quota_us
'};
796 if ($cfs_period_us && $cfs_quota_us) {
797 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
801 $d->{maxdisk} = defined($conf->{'pve
.disksize
'}) ?
802 int($conf->{'pve
.disksize
'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
804 if (my $private = $conf->{'lxc
.rootfs
'}) {
805 if ($private =~ m!^/!) {
806 my $res = PVE::Tools::df($private, 2);
807 $d->{disk} = $res->{used};
808 $d->{maxdisk} = $res->{total};
810 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
811 my $res = get_container_disk_usage($vmid);
812 $d->{disk} = $res->{used};
813 $d->{maxdisk} = $res->{total};
820 $d->{maxmem} = ($conf->{'lxc
.cgroup
.memory
.limit_in_bytes
'}||0) +
821 ($conf->{'lxc
.cgroup
.memory
.memsw
.limit_in_bytes
'}||0);
833 foreach my $vmid (keys %$list) {
834 my $d = $list->{$vmid};
835 next if $d->{status} ne 'running
';
837 $d->{uptime} = 100; # fixme:
839 $d->{mem} = read_cgroup_value('memory
', $vmid, 'memory
.usage_in_bytes
');
840 $d->{swap} = read_cgroup_value('memory
', $vmid, 'memory
.memsw
.usage_in_bytes
') - $d->{mem};
842 my $blkio_bytes = read_cgroup_value('blkio
', $vmid, 'blkio
.throttle
.io_service_bytes
', 1);
843 my @bytes = split(/\n/, $blkio_bytes);
844 foreach my $byte (@bytes) {
845 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
846 $d->{diskread} = $2 if $key eq 'Read
';
847 $d->{diskwrite} = $2 if $key eq 'Write
';
856 sub print_lxc_network {
859 die "no network name defined\n" if !$net->{name};
861 my $res = "name=$net->{name}";
863 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
864 next if !defined($net->{$k});
865 $res .= ",$k=$net->{$k}";
871 sub parse_lxc_network
{
876 return $res if !$data;
878 foreach my $pv (split (/,/, $data)) {
879 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
886 $res->{type
} = 'veth';
887 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
892 sub read_cgroup_value
{
893 my ($group, $vmid, $name, $full) = @_;
895 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
897 return PVE
::Tools
::file_get_contents
($path) if $full;
899 return PVE
::Tools
::file_read_firstline
($path);
902 sub write_cgroup_value
{
903 my ($group, $vmid, $name, $value) = @_;
905 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
906 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
910 sub find_lxc_console_pids
{
914 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
917 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
920 my @args = split(/\0/, $cmdline);
922 # serach for lxc-console -n <vmid>
923 return if scalar(@args) != 3;
924 return if $args[1] ne '-n';
925 return if $args[2] !~ m/^\d+$/;
926 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
930 push @{$res->{$vmid}}, $pid;
942 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
944 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
946 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
951 my $ipv4_reverse_mask = [
987 # Note: we cannot use Net:IP, because that only allows strict
989 sub parse_ipv4_cidr
{
990 my ($cidr, $noerr) = @_;
992 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
993 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
996 return undef if $noerr;
998 die "unable to parse ipv4 address/mask\n";
1004 die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
1007 sub lxc_conf_to_pve
{
1008 my ($vmid, $lxc_conf) = @_;
1010 my $properties = json_config_properties
();
1012 my $conf = { digest
=> $lxc_conf->{digest
} };
1014 foreach my $k (keys %$properties) {
1016 if ($k eq 'description') {
1017 if (my $raw = $lxc_conf->{'pve.comment'}) {
1018 $conf->{$k} = PVE
::Tools
::decode_text
($raw);
1020 } elsif ($k eq 'onboot') {
1021 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
1022 } elsif ($k eq 'startup') {
1023 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
1024 } elsif ($k eq 'hostname') {
1025 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
1026 } elsif ($k eq 'nameserver') {
1027 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
1028 } elsif ($k eq 'searchdomain') {
1029 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
1030 } elsif ($k eq 'memory') {
1031 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
1032 $conf->{$k} = int($value / (1024*1024));
1034 } elsif ($k eq 'swap') {
1035 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
1036 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
1037 $conf->{$k} = int(($value -$mem) / (1024*1024));
1039 } elsif ($k eq 'cpulimit') {
1040 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
1041 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1043 if ($cfs_period_us && $cfs_quota_us) {
1044 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
1048 } elsif ($k eq 'cpuunits') {
1049 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
1050 } elsif ($k eq 'disk') {
1051 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
1052 $lxc_conf->{'pve.disksize'} : 0;
1053 } elsif ($k =~ m/^net\d$/) {
1054 my $net = $lxc_conf->{$k};
1056 $conf->{$k} = print_lxc_network
($net);
1060 if (my $parent = $lxc_conf->{'pve.parent'}) {
1061 $conf->{parent
} = $lxc_conf->{'pve.parent'};
1064 if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
1065 $conf->{description
} = $lxc_conf->{'pve.snapcomment'};
1068 if (my $parent = $lxc_conf->{'pve.snaptime'}) {
1069 $conf->{snaptime
} = $lxc_conf->{'pve.snaptime'};
1075 # verify and cleanup nameserver list (replace \0 with ' ')
1076 sub verify_nameserver_list
{
1077 my ($nameserver_list) = @_;
1080 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1081 PVE
::JSONSchema
::pve_verify_ip
($server);
1082 push @list, $server;
1085 return join(' ', @list);
1088 sub verify_searchdomain_list
{
1089 my ($searchdomain_list) = @_;
1092 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1093 # todo: should we add checks for valid dns domains?
1094 push @list, $server;
1097 return join(' ', @list);
1100 sub update_lxc_config
{
1101 my ($vmid, $conf, $running, $param, $delete) = @_;
1107 my $pid = find_lxc_pid
($vmid);
1108 $rootdir = "/proc/$pid/root";
1111 if (defined($delete)) {
1112 foreach my $opt (@$delete) {
1113 if ($opt eq 'hostname' || $opt eq 'memory') {
1114 die "unable to delete required option '$opt'\n";
1115 } elsif ($opt eq 'swap') {
1116 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
1117 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1118 } elsif ($opt eq 'description') {
1119 delete $conf->{'pve.comment'};
1120 } elsif ($opt eq 'onboot') {
1121 delete $conf->{'pve.onboot'};
1122 } elsif ($opt eq 'startup') {
1123 delete $conf->{'pve.startup'};
1124 } elsif ($opt eq 'nameserver') {
1125 delete $conf->{'pve.nameserver'};
1126 push @nohotplug, $opt;
1128 } elsif ($opt eq 'searchdomain') {
1129 delete $conf->{'pve.searchdomain'};
1130 push @nohotplug, $opt;
1132 } elsif ($opt =~ m/^net(\d)$/) {
1133 delete $conf->{$opt};
1136 PVE
::Network
::veth_delete
("veth${vmid}.$netid");
1140 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1144 foreach my $opt (keys %$param) {
1145 my $value = $param->{$opt};
1146 if ($opt eq 'hostname') {
1147 $conf->{'lxc.utsname'} = $value;
1148 } elsif ($opt eq 'onboot') {
1149 $conf->{'pve.onboot'} = $value ?
1 : 0;
1150 } elsif ($opt eq 'startup') {
1151 $conf->{'pve.startup'} = $value;
1152 } elsif ($opt eq 'nameserver') {
1153 my $list = verify_nameserver_list
($value);
1154 $conf->{'pve.nameserver'} = $list;
1155 push @nohotplug, $opt;
1157 } elsif ($opt eq 'searchdomain') {
1158 my $list = verify_searchdomain_list
($value);
1159 $conf->{'pve.searchdomain'} = $list;
1160 push @nohotplug, $opt;
1162 } elsif ($opt eq 'memory') {
1163 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
1164 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
1165 } elsif ($opt eq 'swap') {
1166 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
1167 $mem = $param->{memory
}*1024*1024 if $param->{memory
};
1168 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
1169 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
1171 } elsif ($opt eq 'cpulimit') {
1173 my $cfs_period_us = 100000;
1174 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
1175 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
1176 write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
1178 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1179 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1180 write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
1182 } elsif ($opt eq 'cpuunits') {
1183 $conf->{'lxc.cgroup.cpu.shares'} = $value;
1184 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1185 } elsif ($opt eq 'description') {
1186 $conf->{'pve.comment'} = PVE
::Tools
::encode_text
($value);
1187 } elsif ($opt eq 'disk') {
1188 $conf->{'pve.disksize'} = $value;
1189 push @nohotplug, $opt;
1191 } elsif ($opt =~ m/^net(\d+)$/) {
1193 my $net = PVE
::LXC
::parse_lxc_network
($value);
1194 $net->{'veth.pair'} = "veth${vmid}.$netid";
1196 $conf->{$opt} = $net;
1198 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1201 die "implement me: $opt";
1203 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1206 if ($running && scalar(@nohotplug)) {
1207 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1211 sub get_primary_ips
{
1214 # return data from net0
1216 my $net = $conf->{net0
};
1217 return undef if !$net;
1219 my $ipv4 = $net->{ip
};
1220 $ipv4 =~ s!/\d+$!! if $ipv4;
1221 my $ipv6 = $net->{ip
};
1222 $ipv6 =~ s!/\d+$!! if $ipv6;
1224 return ($ipv4, $ipv6);
1227 sub destory_lxc_container
{
1228 my ($storage_cfg, $vmid, $conf) = @_;
1230 if (my $volid = $conf->{'pve.volid'}) {
1232 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1233 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1235 PVE
::Storage
::vdisk_free
($storage_cfg, $volid);
1237 destroy_config
($vmid);
1240 my $cmd = ['lxc-destroy', '-n', $vmid ];
1242 PVE
::Tools
::run_command
($cmd);
1246 my $safe_num_ne = sub {
1249 return 0 if !defined($a) && !defined($b);
1250 return 1 if !defined($a);
1251 return 1 if !defined($b);
1256 my $safe_string_ne = sub {
1259 return 0 if !defined($a) && !defined($b);
1260 return 1 if !defined($a);
1261 return 1 if !defined($b);
1267 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1269 my $veth = $newnet->{'veth.pair'};
1270 my $vethpeer = $veth . "p";
1271 my $eth = $newnet->{name
};
1273 if ($conf->{$opt}) {
1274 if (&$safe_string_ne($conf->{$opt}->{hwaddr
}, $newnet->{hwaddr
}) ||
1275 &$safe_string_ne($conf->{$opt}->{name
}, $newnet->{name
})) {
1277 PVE
::Network
::veth_delete
($veth);
1278 delete $conf->{$opt};
1279 PVE
::LXC
::write_config
($vmid, $conf);
1281 hotplug_net
($vmid, $conf, $opt, $newnet);
1283 } elsif (&$safe_string_ne($conf->{$opt}->{bridge
}, $newnet->{bridge
}) ||
1284 &$safe_num_ne($conf->{$opt}->{tag
}, $newnet->{tag
}) ||
1285 &$safe_num_ne($conf->{$opt}->{firewall
}, $newnet->{firewall
})) {
1287 if ($conf->{$opt}->{bridge
}){
1288 PVE
::Network
::tap_unplug
($veth);
1289 delete $conf->{$opt}->{bridge
};
1290 delete $conf->{$opt}->{tag
};
1291 delete $conf->{$opt}->{firewall
};
1292 PVE
::LXC
::write_config
($vmid, $conf);
1295 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1296 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1297 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1298 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1299 PVE
::LXC
::write_config
($vmid, $conf);
1302 hotplug_net
($vmid, $conf, $opt, $newnet);
1305 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1309 my ($vmid, $conf, $opt, $newnet) = @_;
1311 my $veth = $newnet->{'veth.pair'};
1312 my $vethpeer = $veth . "p";
1313 my $eth = $newnet->{name
};
1315 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1316 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1318 # attach peer in container
1319 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1320 PVE
::Tools
::run_command
($cmd);
1322 # link up peer in container
1323 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1324 PVE
::Tools
::run_command
($cmd);
1326 $conf->{$opt}->{type
} = 'veth';
1327 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1328 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1329 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1330 $conf->{$opt}->{hwaddr
} = $newnet->{hwaddr
} if $newnet->{hwaddr
};
1331 $conf->{$opt}->{name
} = $newnet->{name
} if $newnet->{name
};
1332 $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
1334 delete $conf->{$opt}->{ip
} if $conf->{$opt}->{ip
};
1335 delete $conf->{$opt}->{ip6
} if $conf->{$opt}->{ip6
};
1336 delete $conf->{$opt}->{gw
} if $conf->{$opt}->{gw
};
1337 delete $conf->{$opt}->{gw6
} if $conf->{$opt}->{gw6
};
1339 PVE
::LXC
::write_config
($vmid, $conf);
1342 sub update_ipconfig
{
1343 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1345 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1347 my $optdata = $conf->{$opt};
1351 my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
1352 PVE
::Tools
::run_command
($cmd);
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 $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
1364 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
1366 return if !$change_ip && !$change_gw;
1368 # step 1: add new IP, if this fails we cancel
1369 if ($change_ip && $newnet->{$ip}) {
1370 eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
1377 # step 2: replace gateway
1378 # If this fails we delete the added IP and cancel.
1379 # If it succeeds we save the config and delete the old IP, ignoring
1380 # errors. The config is then saved.
1381 # Note: 'ip route replace' can add
1383 if ($newnet->{$gw}) {
1384 eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
1387 # the route was not replaced, the old IP is still available
1388 # rollback (delete new IP) and cancel
1390 eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
1391 warn $@ if $@; # no need to die here
1396 eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
1397 # if the route was not deleted, the guest might have deleted it manually
1403 # from this point on we safe the configuration
1404 # step 3: delete old IP ignoring errors
1405 if ($change_ip && $optdata->{$ip}) {
1406 eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
1407 warn $@ if $@; # no need to die here
1410 foreach my $property ($ip, $gw) {
1411 if ($newnet->{$property}) {
1412 $optdata->{$property} = $newnet->{$property};
1414 delete $optdata->{$property};
1417 PVE
::LXC
::write_config
($vmid, $conf);
1418 $lxc_setup->setup_network($conf);
1421 &$change_ip_config(4);
1422 &$change_ip_config(6);
1426 # Internal snapshots
1428 # NOTE: Snapshot create/delete involves several non-atomic
1429 # action, and can take a long time.
1430 # So we try to avoid locking the file and use 'lock' variable
1431 # inside the config file instead.
1433 my $snapshot_copy_config = sub {
1434 my ($source, $dest) = @_;
1436 foreach my $k (keys %$source) {
1437 next if $k eq 'snapshots';
1438 next if $k eq 'pve.snapstate';
1439 next if $k eq 'pve.snaptime';
1440 next if $k eq 'pve.lock';
1441 next if $k eq 'digest';
1442 next if $k eq 'pve.comment';
1444 $dest->{$k} = $source->{$k};
1448 my $snapshot_prepare = sub {
1449 my ($vmid, $snapname, $comment) = @_;
1453 my $updatefn = sub {
1455 my $conf = load_config
($vmid);
1459 $conf->{'pve.lock'} = 'snapshot';
1461 die "snapshot name '$snapname' already used\n"
1462 if defined($conf->{snapshots
}->{$snapname});
1464 my $storecfg = PVE
::Storage
::config
();
1465 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1467 $snap = $conf->{snapshots
}->{$snapname} = {};
1469 &$snapshot_copy_config($conf, $snap);
1471 $snap->{'pve.snapstate'} = "prepare";
1472 $snap->{'pve.snaptime'} = time();
1473 $snap->{'pve.snapname'} = $snapname;
1474 $snap->{'pve.snapcomment'} = $comment if $comment;
1475 $conf->{snapshots
}->{$snapname} = $snap;
1477 PVE
::LXC
::write_config
($vmid, $conf);
1480 lock_container
($vmid, 10, $updatefn);
1485 my $snapshot_commit = sub {
1486 my ($vmid, $snapname) = @_;
1488 my $updatefn = sub {
1490 my $conf = load_config
($vmid);
1492 die "missing snapshot lock\n"
1493 if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
1495 die "snapshot '$snapname' does not exist\n"
1496 if !defined($conf->{snapshots
}->{$snapname});
1498 die "wrong snapshot state\n"
1499 if !($conf->{snapshots
}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots
}->{$snapname}->{'pve.snapstate'} eq "prepare");
1501 delete $conf->{snapshots
}->{$snapname}->{'pve.snapstate'};
1502 delete $conf->{'pve.lock'};
1503 $conf->{'pve.parent'} = $snapname;
1505 PVE
::LXC
::write_config
($vmid, $conf);
1509 lock_container
($vmid, 10 ,$updatefn);
1513 my ($feature, $conf, $storecfg, $snapname) = @_;
1514 #Fixme add other drives if necessary.
1516 my $volid = $conf->{'pve.volid'};
1517 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $volid, $snapname);
1519 return $err ?
0 : 1;
1522 sub snapshot_create
{
1523 my ($vmid, $snapname, $comment) = @_;
1525 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1527 my $config = load_config
($vmid);
1529 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1530 my $running = check_running
($vmid);
1533 PVE
::Tools
::run_command
($cmd);
1536 my $storecfg = PVE
::Storage
::config
();
1537 my $volid = $config->{'pve.volid'};
1539 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1541 PVE
::Tools
::run_command
($cmd);
1544 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1545 &$snapshot_commit($vmid, $snapname);
1548 #ToDo implement delete snapshot
1553 sub snapshot_delete
{
1554 my ($vmid, $snapname, $force) = @_;
1556 print "Not implemented\n";