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'";
295 my $network_counter = 0;
296 my $network_list = [];
297 my $host_ifnames = {};
301 my $find_next_hostif_name = sub {
302 for (my $i = 0; $i < 10; $i++) {
303 my $name = "veth
${vmid
}.$i";
304 if (!$host_ifnames->{$name}) {
305 $host_ifnames->{$name} = 1;
310 die "unable to find free host_ifname
"; # should not happen
313 my $push_network = sub {
316 push @{$network_list}, $netconf;
318 if (my $netname = $netconf->{'veth.pair'}) {
319 if ($netname =~ m/^veth(\d+).(\d)$/) {
320 die "wrong vmid
for network interface pair
\n" if $1 != $vmid;
321 my $host_ifnames->{$netname} = 1;
323 die "wrong network interface pair
\n";
328 my $finalize_section = sub {
329 &$push_network($network); # flush
331 foreach my $net (@{$network_list}) {
332 next if $net->{type} eq 'empty'; # skip
333 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
334 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
335 die "unsupported network type
'$net->{type}'\n" if $net->{type} ne 'veth';
337 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
339 $data->{snapshots}->{$snapname}->{"net
$1"} = $net;
341 $data->{"net
$1"} = $net;
347 $network_counter = 0;
353 while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
355 next if $line =~ m/^\s*$/; # skip empty lines
356 next if $line =~ m/^#/; # skip comments
358 # snap.pve.snapname starts new sections
359 if ($line =~ m/^(snap\.)?pve\.snapname\s*=\s*(\w*)\s*$/) {
362 &$finalize_section();
365 $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
367 } elsif ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
368 my ($subkey, $value) = ($2, $3);
369 if ($subkey eq 'type') {
370 &$push_network($network);
371 $network = { type => $value };
372 } elsif ($valid_lxc_network_keys->{$subkey}) {
373 $network->{$subkey} = $value;
375 die "unable to parse config line
: $line\n";
377 } elsif ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
378 my ($subkey, $value) = ($2, $3);
379 if ($valid_pve_network_keys->{$subkey}) {
380 $network->{$subkey} = $value;
382 die "unable to parse config line
: $line\n";
384 } elsif ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
385 my ($name, $value) = ($2, $3);
387 if ($lxc_array_configs->{$name}) {
388 $data->{$name} = [] if !defined($data->{$name});
390 push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value);
392 push @{$data->{$name}}, parse_lxc_option($name, $value);
396 die "multiple definitions
for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
397 $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
399 die "multiple definitions
for $name\n" if defined($data->{$name});
400 $data->{$name} = parse_lxc_option($name, $value);
404 die "unable to parse config line
: $line\n";
408 &$finalize_section();
414 my $vmlist = PVE::Cluster::get_vmlist();
416 return $res if !$vmlist || !$vmlist->{ids};
417 my $ids = $vmlist->{ids};
419 foreach my $vmid (keys %$ids) {
420 next if !$vmid; # skip CT0
421 my $d = $ids->{$vmid};
422 next if !$d->{node} || $d->{node} ne $nodename;
423 next if !$d->{type} || $d->{type} ne 'lxc';
424 $res->{$vmid}->{type} = 'lxc';
429 sub cfs_config_path {
430 my ($vmid, $node) = @_;
432 $node = $nodename if !$node;
433 return "nodes
/$node/lxc/$vmid/config";
437 my ($vmid, $node) = @_;
439 my $cfspath = cfs_config_path($vmid, $node);
440 return "/etc/pve
/$cfspath";
446 my $cfspath = cfs_config_path($vmid);
448 my $conf = PVE::Cluster::cfs_read_file($cfspath);
449 die "container
$vmid does not exists\n" if !defined($conf);
455 my ($vmid, $conf) = @_;
457 my $dir = "/etc/pve
/nodes/$nodename/lxc";
461 mkdir($dir) || die "unable to create container configuration directory
- $!\n";
463 write_config($vmid, $conf);
469 my $dir = "/etc/pve
/nodes/$nodename/lxc/$vmid";
470 File::Path::rmtree($dir);
474 my ($vmid, $conf) = @_;
476 my $cfspath = cfs_config_path($vmid);
478 PVE::Cluster::cfs_write_file($cfspath, $conf);
482 sub write_temp_config {
483 my ($vmid, $conf) = @_;
486 my $filename = "/tmp/temp-lxc-conf-
$vmid-$$-$tempcounter.conf
";
488 my $raw = write_lxc_config($filename, $conf);
490 PVE::Tools::file_set_contents($filename, $raw);
495 # flock: we use one file handle per process, so lock file
496 # can be called multiple times and succeeds for the same process.
498 my $lock_handles = {};
499 my $lockdir = "/run/lock
/lxc
";
504 return "$lockdir/pve-config
-{$vmid}.lock";
508 my ($vmid, $timeout) = @_;
510 $timeout = 10 if !$timeout;
513 my $filename = lock_filename($vmid);
515 mkdir $lockdir if !-d $lockdir;
517 my $lock_func = sub {
518 if (!$lock_handles->{$$}->{$filename}) {
519 my $fh = new IO::File(">>$filename") ||
520 die "can
't open file - $!\n";
521 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
524 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
525 print STDERR "trying to aquire lock...";
528 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
529 # try again on EINTR (see bug #273)
530 if ($success || ($! != EINTR)) {
535 print STDERR " failed\n";
536 die "can't aquire
lock - $!\n";
539 $lock_handles->{$$}->{$filename}->{refcount}++;
541 print STDERR " OK
\n";
545 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
548 die "can
't lock file '$filename' - $err";
555 my $filename = lock_filename($vmid);
557 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
558 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
559 if ($refcount <= 0) {
560 $lock_handles->{$$}->{$filename} = undef;
567 my ($vmid, $timeout, $code, @param) = @_;
571 lock_aquire($vmid, $timeout);
572 eval { $res = &$code(@param) };
585 description => "Specifies whether a VM will be started during system bootup.",
588 startup => get_standard_option('pve-startup-order
'),
592 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.",
600 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.",
608 description => "Amount of RAM for the VM in MB.",
615 description => "Amount of SWAP for the VM in MB.",
622 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
628 description => "Set a host name for the container.",
635 description => "Container description. Only used on the configuration web interface.",
640 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
645 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.",
649 my $MAX_LXC_NETWORKS = 10;
650 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
651 $confdesc->{"net$i"} = {
653 type => 'string
', format => 'pve-lxc-network
',
654 description => "Specifies network interfaces for the container.\n\n".
655 "The string should have the follow format:\n\n".
656 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
657 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
658 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
659 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
666 return defined($confdesc->{$name});
669 # add JSON properties for create and set function
670 sub json_config_properties {
673 foreach my $opt (keys %$confdesc) {
674 $prop->{$opt} = $confdesc->{$opt};
680 # container status helpers
682 sub list_active_containers {
684 my $filename = "/proc/net/unix";
686 # similar test is used by lcxcontainers.c: list_active_containers
689 my $fh = IO::File->new ($filename, "r");
692 while (defined(my $line = <$fh>)) {
693 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
695 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
706 # warning: this is slow
710 my $active_hash = list_active_containers();
712 return 1 if defined($active_hash->{$vmid});
717 sub get_container_disk_usage {
720 my $cmd = ['lxc-attach
', '-n
', $vmid, '--', 'df
', '-P
', '-B
', '1', '/'];
730 if (my ($fsid, $total, $used, $avail) = $line =~
731 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
739 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
748 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc
' }} : config_list();
750 my $active_hash = list_active_containers();
752 foreach my $vmid (keys %$list) {
753 my $d = $list->{$vmid};
755 my $running = defined($active_hash->{$vmid});
757 $d->{status} = $running ? 'running
' : 'stopped
';
759 my $cfspath = cfs_config_path($vmid);
760 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
762 $d->{name} = $conf->{'lxc
.utsname
'} || "CT$vmid";
763 $d->{name} =~ s/[\s]//g;
767 my $cfs_period_us = $conf->{'lxc
.cgroup
.cpu
.cfs_period_us
'};
768 my $cfs_quota_us = $conf->{'lxc
.cgroup
.cpu
.cfs_quota_us
'};
770 if ($cfs_period_us && $cfs_quota_us) {
771 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
775 $d->{maxdisk} = defined($conf->{'pve
.disksize
'}) ?
776 int($conf->{'pve
.disksize
'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
778 if (my $private = $conf->{'lxc
.rootfs
'}) {
779 if ($private =~ m!^/!) {
780 my $res = PVE::Tools::df($private, 2);
781 $d->{disk} = $res->{used};
782 $d->{maxdisk} = $res->{total};
784 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
785 my $res = get_container_disk_usage($vmid);
786 $d->{disk} = $res->{used};
787 $d->{maxdisk} = $res->{total};
794 $d->{maxmem} = ($conf->{'lxc
.cgroup
.memory
.limit_in_bytes
'}||0) +
795 ($conf->{'lxc
.cgroup
.memory
.memsw
.limit_in_bytes
'}||0);
807 foreach my $vmid (keys %$list) {
808 my $d = $list->{$vmid};
809 next if $d->{status} ne 'running
';
811 $d->{uptime} = 100; # fixme:
813 $d->{mem} = read_cgroup_value('memory
', $vmid, 'memory
.usage_in_bytes
');
814 $d->{swap} = read_cgroup_value('memory
', $vmid, 'memory
.memsw
.usage_in_bytes
') - $d->{mem};
816 my $blkio_bytes = read_cgroup_value('blkio
', $vmid, 'blkio
.throttle
.io_service_bytes
', 1);
817 my @bytes = split(/\n/, $blkio_bytes);
818 foreach my $byte (@bytes) {
819 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
820 $d->{diskread} = $2 if $key eq 'Read
';
821 $d->{diskwrite} = $2 if $key eq 'Write
';
830 sub print_lxc_network {
833 die "no network name defined\n" if !$net->{name};
835 my $res = "name=$net->{name}";
837 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
838 next if !defined($net->{$k});
839 $res .= ",$k=$net->{$k}";
845 sub parse_lxc_network
{
850 return $res if !$data;
852 foreach my $pv (split (/,/, $data)) {
853 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
860 $res->{type
} = 'veth';
861 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
866 sub read_cgroup_value
{
867 my ($group, $vmid, $name, $full) = @_;
869 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
871 return PVE
::Tools
::file_get_contents
($path) if $full;
873 return PVE
::Tools
::file_read_firstline
($path);
876 sub write_cgroup_value
{
877 my ($group, $vmid, $name, $value) = @_;
879 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
880 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
884 sub find_lxc_console_pids
{
888 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
891 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
894 my @args = split(/\0/, $cmdline);
896 # serach for lxc-console -n <vmid>
897 return if scalar(@args) != 3;
898 return if $args[1] ne '-n';
899 return if $args[2] !~ m/^\d+$/;
900 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
904 push @{$res->{$vmid}}, $pid;
916 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
918 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
920 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
925 my $ipv4_reverse_mask = [
961 # Note: we cannot use Net:IP, because that only allows strict
963 sub parse_ipv4_cidr
{
964 my ($cidr, $noerr) = @_;
966 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
967 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
970 return undef if $noerr;
972 die "unable to parse ipv4 address/mask\n";
978 die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
981 sub lxc_conf_to_pve
{
982 my ($vmid, $lxc_conf) = @_;
984 my $properties = json_config_properties
();
986 my $conf = { digest
=> $lxc_conf->{digest
} };
988 foreach my $k (keys %$properties) {
990 if ($k eq 'description') {
991 if (my $raw = $lxc_conf->{'pve.comment'}) {
992 $conf->{$k} = PVE
::Tools
::decode_text
($raw);
994 } elsif ($k eq 'onboot') {
995 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
996 } elsif ($k eq 'startup') {
997 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
998 } elsif ($k eq 'hostname') {
999 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
1000 } elsif ($k eq 'nameserver') {
1001 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
1002 } elsif ($k eq 'searchdomain') {
1003 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
1004 } elsif ($k eq 'memory') {
1005 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
1006 $conf->{$k} = int($value / (1024*1024));
1008 } elsif ($k eq 'swap') {
1009 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
1010 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
1011 $conf->{$k} = int(($value -$mem) / (1024*1024));
1013 } elsif ($k eq 'cpulimit') {
1014 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
1015 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1017 if ($cfs_period_us && $cfs_quota_us) {
1018 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
1022 } elsif ($k eq 'cpuunits') {
1023 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
1024 } elsif ($k eq 'disk') {
1025 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
1026 $lxc_conf->{'pve.disksize'} : 0;
1027 } elsif ($k =~ m/^net\d$/) {
1028 my $net = $lxc_conf->{$k};
1030 $conf->{$k} = print_lxc_network
($net);
1034 if (my $parent = $lxc_conf->{'pve.parent'}) {
1035 $conf->{parent
} = $lxc_conf->{'pve.parent'};
1038 if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
1039 $conf->{description
} = $lxc_conf->{'pve.snapcomment'};
1042 if (my $parent = $lxc_conf->{'pve.snaptime'}) {
1043 $conf->{snaptime
} = $lxc_conf->{'pve.snaptime'};
1049 # verify and cleanup nameserver list (replace \0 with ' ')
1050 sub verify_nameserver_list
{
1051 my ($nameserver_list) = @_;
1054 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1055 PVE
::JSONSchema
::pve_verify_ip
($server);
1056 push @list, $server;
1059 return join(' ', @list);
1062 sub verify_searchdomain_list
{
1063 my ($searchdomain_list) = @_;
1066 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1067 # todo: should we add checks for valid dns domains?
1068 push @list, $server;
1071 return join(' ', @list);
1074 sub update_lxc_config
{
1075 my ($vmid, $conf, $running, $param, $delete) = @_;
1081 my $pid = find_lxc_pid
($vmid);
1082 $rootdir = "/proc/$pid/root";
1085 if (defined($delete)) {
1086 foreach my $opt (@$delete) {
1087 if ($opt eq 'hostname' || $opt eq 'memory') {
1088 die "unable to delete required option '$opt'\n";
1089 } elsif ($opt eq 'swap') {
1090 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
1091 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1092 } elsif ($opt eq 'description') {
1093 delete $conf->{'pve.comment'};
1094 } elsif ($opt eq 'onboot') {
1095 delete $conf->{'pve.onboot'};
1096 } elsif ($opt eq 'startup') {
1097 delete $conf->{'pve.startup'};
1098 } elsif ($opt eq 'nameserver') {
1099 delete $conf->{'pve.nameserver'};
1100 push @nohotplug, $opt;
1102 } elsif ($opt eq 'searchdomain') {
1103 delete $conf->{'pve.searchdomain'};
1104 push @nohotplug, $opt;
1106 } elsif ($opt =~ m/^net(\d)$/) {
1107 delete $conf->{$opt};
1110 PVE
::Network
::veth_delete
("veth${vmid}.$netid");
1114 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1118 foreach my $opt (keys %$param) {
1119 my $value = $param->{$opt};
1120 if ($opt eq 'hostname') {
1121 $conf->{'lxc.utsname'} = $value;
1122 } elsif ($opt eq 'onboot') {
1123 $conf->{'pve.onboot'} = $value ?
1 : 0;
1124 } elsif ($opt eq 'startup') {
1125 $conf->{'pve.startup'} = $value;
1126 } elsif ($opt eq 'nameserver') {
1127 my $list = verify_nameserver_list
($value);
1128 $conf->{'pve.nameserver'} = $list;
1129 push @nohotplug, $opt;
1131 } elsif ($opt eq 'searchdomain') {
1132 my $list = verify_searchdomain_list
($value);
1133 $conf->{'pve.searchdomain'} = $list;
1134 push @nohotplug, $opt;
1136 } elsif ($opt eq 'memory') {
1137 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
1138 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
1139 } elsif ($opt eq 'swap') {
1140 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
1141 $mem = $param->{memory
}*1024*1024 if $param->{memory
};
1142 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
1143 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
1145 } elsif ($opt eq 'cpulimit') {
1147 my $cfs_period_us = 100000;
1148 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
1149 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
1150 write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
1152 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1153 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1154 write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
1156 } elsif ($opt eq 'cpuunits') {
1157 $conf->{'lxc.cgroup.cpu.shares'} = $value;
1158 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1159 } elsif ($opt eq 'description') {
1160 $conf->{'pve.comment'} = PVE
::Tools
::encode_text
($value);
1161 } elsif ($opt eq 'disk') {
1162 $conf->{'pve.disksize'} = $value;
1163 push @nohotplug, $opt;
1165 } elsif ($opt =~ m/^net(\d+)$/) {
1167 my $net = PVE
::LXC
::parse_lxc_network
($value);
1168 $net->{'veth.pair'} = "veth${vmid}.$netid";
1170 $conf->{$opt} = $net;
1172 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1175 die "implement me: $opt";
1177 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1180 if ($running && scalar(@nohotplug)) {
1181 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1185 sub get_primary_ips
{
1188 # return data from net0
1190 my $net = $conf->{net0
};
1191 return undef if !$net;
1193 my $ipv4 = $net->{ip
};
1195 if ($ipv4 =~ /^(dhcp|manual)$/) {
1201 my $ipv6 = $net->{ip6
};
1203 if ($ipv6 =~ /^(dhcp|manual)$/) {
1210 return ($ipv4, $ipv6);
1213 sub destory_lxc_container
{
1214 my ($storage_cfg, $vmid, $conf) = @_;
1216 if (my $volid = $conf->{'pve.volid'}) {
1218 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1219 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1221 PVE
::Storage
::vdisk_free
($storage_cfg, $volid);
1223 destroy_config
($vmid);
1226 my $cmd = ['lxc-destroy', '-n', $vmid ];
1228 PVE
::Tools
::run_command
($cmd);
1232 my $safe_num_ne = sub {
1235 return 0 if !defined($a) && !defined($b);
1236 return 1 if !defined($a);
1237 return 1 if !defined($b);
1242 my $safe_string_ne = sub {
1245 return 0 if !defined($a) && !defined($b);
1246 return 1 if !defined($a);
1247 return 1 if !defined($b);
1253 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1255 my $veth = $newnet->{'veth.pair'};
1256 my $vethpeer = $veth . "p";
1257 my $eth = $newnet->{name
};
1259 if ($conf->{$opt}) {
1260 if (&$safe_string_ne($conf->{$opt}->{hwaddr
}, $newnet->{hwaddr
}) ||
1261 &$safe_string_ne($conf->{$opt}->{name
}, $newnet->{name
})) {
1263 PVE
::Network
::veth_delete
($veth);
1264 delete $conf->{$opt};
1265 PVE
::LXC
::write_config
($vmid, $conf);
1267 hotplug_net
($vmid, $conf, $opt, $newnet);
1269 } elsif (&$safe_string_ne($conf->{$opt}->{bridge
}, $newnet->{bridge
}) ||
1270 &$safe_num_ne($conf->{$opt}->{tag
}, $newnet->{tag
}) ||
1271 &$safe_num_ne($conf->{$opt}->{firewall
}, $newnet->{firewall
})) {
1273 if ($conf->{$opt}->{bridge
}){
1274 PVE
::Network
::tap_unplug
($veth);
1275 delete $conf->{$opt}->{bridge
};
1276 delete $conf->{$opt}->{tag
};
1277 delete $conf->{$opt}->{firewall
};
1278 PVE
::LXC
::write_config
($vmid, $conf);
1281 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1282 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1283 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1284 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1285 PVE
::LXC
::write_config
($vmid, $conf);
1288 hotplug_net
($vmid, $conf, $opt, $newnet);
1291 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1295 my ($vmid, $conf, $opt, $newnet) = @_;
1297 my $veth = $newnet->{'veth.pair'};
1298 my $vethpeer = $veth . "p";
1299 my $eth = $newnet->{name
};
1301 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1302 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1304 # attach peer in container
1305 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1306 PVE
::Tools
::run_command
($cmd);
1308 # link up peer in container
1309 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1310 PVE
::Tools
::run_command
($cmd);
1312 $conf->{$opt}->{type
} = 'veth';
1313 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1314 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1315 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1316 $conf->{$opt}->{hwaddr
} = $newnet->{hwaddr
} if $newnet->{hwaddr
};
1317 $conf->{$opt}->{name
} = $newnet->{name
} if $newnet->{name
};
1318 $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
1320 delete $conf->{$opt}->{ip
} if $conf->{$opt}->{ip
};
1321 delete $conf->{$opt}->{ip6
} if $conf->{$opt}->{ip6
};
1322 delete $conf->{$opt}->{gw
} if $conf->{$opt}->{gw
};
1323 delete $conf->{$opt}->{gw6
} if $conf->{$opt}->{gw6
};
1325 PVE
::LXC
::write_config
($vmid, $conf);
1328 sub update_ipconfig
{
1329 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1331 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1333 my $optdata = $conf->{$opt};
1337 my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
1338 PVE
::Tools
::run_command
($cmd);
1341 my $change_ip_config = sub {
1342 my ($ipversion) = @_;
1344 my $family_opt = "-$ipversion";
1345 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1346 my $gw= "gw$suffix";
1347 my $ip= "ip$suffix";
1349 my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
1350 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
1352 return if !$change_ip && !$change_gw;
1354 # step 1: add new IP, if this fails we cancel
1355 if ($change_ip && $newnet->{$ip}) {
1356 eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
1363 # step 2: replace gateway
1364 # If this fails we delete the added IP and cancel.
1365 # If it succeeds we save the config and delete the old IP, ignoring
1366 # errors. The config is then saved.
1367 # Note: 'ip route replace' can add
1369 if ($newnet->{$gw}) {
1370 eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
1373 # the route was not replaced, the old IP is still available
1374 # rollback (delete new IP) and cancel
1376 eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
1377 warn $@ if $@; # no need to die here
1382 eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
1383 # if the route was not deleted, the guest might have deleted it manually
1389 # from this point on we safe the configuration
1390 # step 3: delete old IP ignoring errors
1391 if ($change_ip && $optdata->{$ip}) {
1392 eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
1393 warn $@ if $@; # no need to die here
1396 foreach my $property ($ip, $gw) {
1397 if ($newnet->{$property}) {
1398 $optdata->{$property} = $newnet->{$property};
1400 delete $optdata->{$property};
1403 PVE
::LXC
::write_config
($vmid, $conf);
1404 $lxc_setup->setup_network($conf);
1407 &$change_ip_config(4);
1408 &$change_ip_config(6);
1412 # Internal snapshots
1414 # NOTE: Snapshot create/delete involves several non-atomic
1415 # action, and can take a long time.
1416 # So we try to avoid locking the file and use 'lock' variable
1417 # inside the config file instead.
1419 my $snapshot_copy_config = sub {
1420 my ($source, $dest) = @_;
1422 foreach my $k (keys %$source) {
1423 next if $k eq 'snapshots';
1424 next if $k eq 'pve.snapstate';
1425 next if $k eq 'pve.snaptime';
1426 next if $k eq 'pve.lock';
1427 next if $k eq 'digest';
1428 next if $k eq 'pve.comment';
1430 $dest->{$k} = $source->{$k};
1434 my $snapshot_prepare = sub {
1435 my ($vmid, $snapname, $comment) = @_;
1439 my $updatefn = sub {
1441 my $conf = load_config
($vmid);
1445 $conf->{'pve.lock'} = 'snapshot';
1447 die "snapshot name '$snapname' already used\n"
1448 if defined($conf->{snapshots
}->{$snapname});
1450 my $storecfg = PVE
::Storage
::config
();
1451 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1453 $snap = $conf->{snapshots
}->{$snapname} = {};
1455 &$snapshot_copy_config($conf, $snap);
1457 $snap->{'pve.snapstate'} = "prepare";
1458 $snap->{'pve.snaptime'} = time();
1459 $snap->{'pve.snapname'} = $snapname;
1460 $snap->{'pve.snapcomment'} = $comment if $comment;
1461 $conf->{snapshots
}->{$snapname} = $snap;
1463 PVE
::LXC
::write_config
($vmid, $conf);
1466 lock_container
($vmid, 10, $updatefn);
1471 my $snapshot_commit = sub {
1472 my ($vmid, $snapname) = @_;
1474 my $updatefn = sub {
1476 my $conf = load_config
($vmid);
1478 die "missing snapshot lock\n"
1479 if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
1481 die "snapshot '$snapname' does not exist\n"
1482 if !defined($conf->{snapshots
}->{$snapname});
1484 die "wrong snapshot state\n"
1485 if !($conf->{snapshots
}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots
}->{$snapname}->{'pve.snapstate'} eq "prepare");
1487 delete $conf->{snapshots
}->{$snapname}->{'pve.snapstate'};
1488 delete $conf->{'pve.lock'};
1489 $conf->{'pve.parent'} = $snapname;
1491 PVE
::LXC
::write_config
($vmid, $conf);
1495 lock_container
($vmid, 10 ,$updatefn);
1499 my ($feature, $conf, $storecfg, $snapname) = @_;
1500 #Fixme add other drives if necessary.
1502 my $volid = $conf->{'pve.volid'};
1503 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $volid, $snapname);
1505 return $err ?
0 : 1;
1508 sub snapshot_create
{
1509 my ($vmid, $snapname, $comment) = @_;
1511 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1513 my $config = load_config
($vmid);
1515 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1516 my $running = check_running
($vmid);
1519 PVE
::Tools
::run_command
($cmd);
1522 my $storecfg = PVE
::Storage
::config
();
1523 my $volid = $config->{'pve.volid'};
1525 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1527 PVE
::Tools
::run_command
($cmd);
1530 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1531 &$snapshot_commit($vmid, $snapname);
1534 snapshot_delete
($vmid, $snapname, 1);
1539 sub snapshot_delete
{
1540 my ($vmid, $snapname, $force) = @_;
1546 my $updatefn = sub {
1548 $conf = load_config
($vmid);
1550 $snap = $conf->{snapshots
}->{$snapname};
1554 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1556 $snap->{'pve.snapstate'} = 'delete';
1558 PVE
::LXC
::write_config
($vmid, $conf);
1561 lock_container
($vmid, 10, $updatefn);
1563 my $storecfg = PVE
::Storage
::config
();
1565 my $del_snap = sub {
1569 if ($conf->{'pve.parent'} eq $snapname) {
1570 if ($conf->{snapshots
}->{$snapname}->{'pve.snapname'}) {
1571 $conf->{'pve.parent'} = $conf->{snapshots
}->{$snapname}->{'pve.parent'};
1573 delete $conf->{'pve.parent'};
1577 delete $conf->{snapshots
}->{$snapname};
1579 PVE
::LXC
::write_config
($vmid, $conf);
1582 my $volid = $conf->{snapshots
}->{$snapname}->{'pve.volid'};
1585 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1589 if(!$err || ($err && $force)) {
1590 lock_container
($vmid, 10, $del_snap);
1592 die "Can't delete snapshot: $vmid $snapname $err\n";
1597 sub snapshot_rollback
{
1598 my ($vmid, $snapname) = @_;
1600 my $storecfg = PVE
::Storage
::config
();
1602 my $conf = load_config
($vmid);
1604 my $snap = $conf->{snapshots
}->{$snapname};
1606 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1608 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $snap->{'pve.volid'},
1611 my $updatefn = sub {
1613 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" if $snap->{snapstate
};
1617 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1619 die "unable to rollback vm $vmid: vm is running\n"
1620 if check_running
($vmid);
1622 $conf->{'pve.lock'} = 'rollback';
1626 # copy snapshot config to current config
1628 my $tmp_conf = $conf;
1629 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1630 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1631 delete $conf->{'pve.snaptime'};
1632 delete $conf->{'pve.snapname'};
1633 $conf->{'pve.parent'} = $snapname;
1635 PVE
::LXC
::write_config
($vmid, $conf);
1638 my $unlockfn = sub {
1639 delete $conf->{'pve.lock'};
1640 PVE
::LXC
::write_config
($vmid, $conf);
1643 lock_container
($vmid, 10, $updatefn);
1645 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $conf->{'pve.volid'}, $snapname);
1647 lock_container
($vmid, 5, $unlockfn);