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
};
1194 $ipv4 =~ s!/\d+$!! if $ipv4;
1195 my $ipv6 = $net->{ip6
};
1196 $ipv6 =~ s!/\d+$!! if $ipv6;
1198 return ($ipv4, $ipv6);
1201 sub destory_lxc_container
{
1202 my ($storage_cfg, $vmid, $conf) = @_;
1204 if (my $volid = $conf->{'pve.volid'}) {
1206 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1207 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1209 PVE
::Storage
::vdisk_free
($storage_cfg, $volid);
1211 destroy_config
($vmid);
1214 my $cmd = ['lxc-destroy', '-n', $vmid ];
1216 PVE
::Tools
::run_command
($cmd);
1220 my $safe_num_ne = sub {
1223 return 0 if !defined($a) && !defined($b);
1224 return 1 if !defined($a);
1225 return 1 if !defined($b);
1230 my $safe_string_ne = sub {
1233 return 0 if !defined($a) && !defined($b);
1234 return 1 if !defined($a);
1235 return 1 if !defined($b);
1241 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1243 my $veth = $newnet->{'veth.pair'};
1244 my $vethpeer = $veth . "p";
1245 my $eth = $newnet->{name
};
1247 if ($conf->{$opt}) {
1248 if (&$safe_string_ne($conf->{$opt}->{hwaddr
}, $newnet->{hwaddr
}) ||
1249 &$safe_string_ne($conf->{$opt}->{name
}, $newnet->{name
})) {
1251 PVE
::Network
::veth_delete
($veth);
1252 delete $conf->{$opt};
1253 PVE
::LXC
::write_config
($vmid, $conf);
1255 hotplug_net
($vmid, $conf, $opt, $newnet);
1257 } elsif (&$safe_string_ne($conf->{$opt}->{bridge
}, $newnet->{bridge
}) ||
1258 &$safe_num_ne($conf->{$opt}->{tag
}, $newnet->{tag
}) ||
1259 &$safe_num_ne($conf->{$opt}->{firewall
}, $newnet->{firewall
})) {
1261 if ($conf->{$opt}->{bridge
}){
1262 PVE
::Network
::tap_unplug
($veth);
1263 delete $conf->{$opt}->{bridge
};
1264 delete $conf->{$opt}->{tag
};
1265 delete $conf->{$opt}->{firewall
};
1266 PVE
::LXC
::write_config
($vmid, $conf);
1269 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1270 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1271 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1272 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1273 PVE
::LXC
::write_config
($vmid, $conf);
1276 hotplug_net
($vmid, $conf, $opt, $newnet);
1279 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1283 my ($vmid, $conf, $opt, $newnet) = @_;
1285 my $veth = $newnet->{'veth.pair'};
1286 my $vethpeer = $veth . "p";
1287 my $eth = $newnet->{name
};
1289 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1290 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1292 # attach peer in container
1293 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1294 PVE
::Tools
::run_command
($cmd);
1296 # link up peer in container
1297 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1298 PVE
::Tools
::run_command
($cmd);
1300 $conf->{$opt}->{type
} = 'veth';
1301 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1302 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1303 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1304 $conf->{$opt}->{hwaddr
} = $newnet->{hwaddr
} if $newnet->{hwaddr
};
1305 $conf->{$opt}->{name
} = $newnet->{name
} if $newnet->{name
};
1306 $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
1308 delete $conf->{$opt}->{ip
} if $conf->{$opt}->{ip
};
1309 delete $conf->{$opt}->{ip6
} if $conf->{$opt}->{ip6
};
1310 delete $conf->{$opt}->{gw
} if $conf->{$opt}->{gw
};
1311 delete $conf->{$opt}->{gw6
} if $conf->{$opt}->{gw6
};
1313 PVE
::LXC
::write_config
($vmid, $conf);
1316 sub update_ipconfig
{
1317 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1319 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1321 my $optdata = $conf->{$opt};
1325 my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
1326 PVE
::Tools
::run_command
($cmd);
1329 my $change_ip_config = sub {
1330 my ($ipversion) = @_;
1332 my $family_opt = "-$ipversion";
1333 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1334 my $gw= "gw$suffix";
1335 my $ip= "ip$suffix";
1337 my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
1338 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
1340 return if !$change_ip && !$change_gw;
1342 # step 1: add new IP, if this fails we cancel
1343 if ($change_ip && $newnet->{$ip}) {
1344 eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
1351 # step 2: replace gateway
1352 # If this fails we delete the added IP and cancel.
1353 # If it succeeds we save the config and delete the old IP, ignoring
1354 # errors. The config is then saved.
1355 # Note: 'ip route replace' can add
1357 if ($newnet->{$gw}) {
1358 eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
1361 # the route was not replaced, the old IP is still available
1362 # rollback (delete new IP) and cancel
1364 eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
1365 warn $@ if $@; # no need to die here
1370 eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
1371 # if the route was not deleted, the guest might have deleted it manually
1377 # from this point on we safe the configuration
1378 # step 3: delete old IP ignoring errors
1379 if ($change_ip && $optdata->{$ip}) {
1380 eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
1381 warn $@ if $@; # no need to die here
1384 foreach my $property ($ip, $gw) {
1385 if ($newnet->{$property}) {
1386 $optdata->{$property} = $newnet->{$property};
1388 delete $optdata->{$property};
1391 PVE
::LXC
::write_config
($vmid, $conf);
1392 $lxc_setup->setup_network($conf);
1395 &$change_ip_config(4);
1396 &$change_ip_config(6);
1400 # Internal snapshots
1402 # NOTE: Snapshot create/delete involves several non-atomic
1403 # action, and can take a long time.
1404 # So we try to avoid locking the file and use 'lock' variable
1405 # inside the config file instead.
1407 my $snapshot_copy_config = sub {
1408 my ($source, $dest) = @_;
1410 foreach my $k (keys %$source) {
1411 next if $k eq 'snapshots';
1412 next if $k eq 'pve.snapstate';
1413 next if $k eq 'pve.snaptime';
1414 next if $k eq 'pve.lock';
1415 next if $k eq 'digest';
1416 next if $k eq 'pve.comment';
1418 $dest->{$k} = $source->{$k};
1422 my $snapshot_prepare = sub {
1423 my ($vmid, $snapname, $comment) = @_;
1427 my $updatefn = sub {
1429 my $conf = load_config
($vmid);
1433 $conf->{'pve.lock'} = 'snapshot';
1435 die "snapshot name '$snapname' already used\n"
1436 if defined($conf->{snapshots
}->{$snapname});
1438 my $storecfg = PVE
::Storage
::config
();
1439 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1441 $snap = $conf->{snapshots
}->{$snapname} = {};
1443 &$snapshot_copy_config($conf, $snap);
1445 $snap->{'pve.snapstate'} = "prepare";
1446 $snap->{'pve.snaptime'} = time();
1447 $snap->{'pve.snapname'} = $snapname;
1448 $snap->{'pve.snapcomment'} = $comment if $comment;
1449 $conf->{snapshots
}->{$snapname} = $snap;
1451 PVE
::LXC
::write_config
($vmid, $conf);
1454 lock_container
($vmid, 10, $updatefn);
1459 my $snapshot_commit = sub {
1460 my ($vmid, $snapname) = @_;
1462 my $updatefn = sub {
1464 my $conf = load_config
($vmid);
1466 die "missing snapshot lock\n"
1467 if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
1469 die "snapshot '$snapname' does not exist\n"
1470 if !defined($conf->{snapshots
}->{$snapname});
1472 die "wrong snapshot state\n"
1473 if !($conf->{snapshots
}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots
}->{$snapname}->{'pve.snapstate'} eq "prepare");
1475 delete $conf->{snapshots
}->{$snapname}->{'pve.snapstate'};
1476 delete $conf->{'pve.lock'};
1477 $conf->{'pve.parent'} = $snapname;
1479 PVE
::LXC
::write_config
($vmid, $conf);
1483 lock_container
($vmid, 10 ,$updatefn);
1487 my ($feature, $conf, $storecfg, $snapname) = @_;
1488 #Fixme add other drives if necessary.
1490 my $volid = $conf->{'pve.volid'};
1491 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $volid, $snapname);
1493 return $err ?
0 : 1;
1496 sub snapshot_create
{
1497 my ($vmid, $snapname, $comment) = @_;
1499 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1501 my $config = load_config
($vmid);
1503 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1504 my $running = check_running
($vmid);
1507 PVE
::Tools
::run_command
($cmd);
1510 my $storecfg = PVE
::Storage
::config
();
1511 my $volid = $config->{'pve.volid'};
1513 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1515 PVE
::Tools
::run_command
($cmd);
1518 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1519 &$snapshot_commit($vmid, $snapname);
1522 snapshot_delete
($vmid, $snapname, 1);
1527 sub snapshot_delete
{
1528 my ($vmid, $snapname, $force) = @_;
1534 my $updatefn = sub {
1536 $conf = load_config
($vmid);
1538 $snap = $conf->{snapshots
}->{$snapname};
1542 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1544 $snap->{'pve.snapstate'} = 'delete';
1546 PVE
::LXC
::write_config
($vmid, $conf);
1549 lock_container
($vmid, 10, $updatefn);
1551 my $storecfg = PVE
::Storage
::config
();
1553 my $del_snap = sub {
1557 if ($conf->{'pve.parent'} eq $snapname) {
1558 if ($conf->{snapshots
}->{$snapname}->{'pve.snapname'}) {
1559 $conf->{'pve.parent'} = $conf->{snapshots
}->{$snapname}->{'pve.parent'};
1561 delete $conf->{'pve.parent'};
1565 delete $conf->{snapshots
}->{$snapname};
1567 PVE
::LXC
::write_config
($vmid, $conf);
1570 my $volid = $conf->{snapshots
}->{$snapname}->{'pve.volid'};
1573 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1577 if(!$err || ($err && $force)) {
1578 lock_container
($vmid, 10, $del_snap);
1580 die "Can't delete snapshot: $vmid $snapname $err\n";
1585 sub snapshot_rollback
{
1586 my ($vmid, $snapname) = @_;
1588 my $storecfg = PVE
::Storage
::config
();
1590 my $conf = load_config
($vmid);
1592 my $snap = $conf->{snapshots
}->{$snapname};
1594 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1596 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $snap->{'pve.volid'},
1599 my $updatefn = sub {
1601 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" if $snap->{snapstate
};
1605 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1607 die "unable to rollback vm $vmid: vm is running\n"
1608 if check_running
($vmid);
1610 $conf->{'pve.lock'} = 'rollback';
1614 # copy snapshot config to current config
1616 my $tmp_conf = $conf;
1617 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1618 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1619 delete $conf->{'pve.snaptime'};
1620 delete $conf->{'pve.snapname'};
1621 $conf->{'pve.parent'} = $snapname;
1623 PVE
::LXC
::write_config
($vmid, $conf);
1626 my $unlockfn = sub {
1627 delete $conf->{'pve.lock'};
1628 PVE
::LXC
::write_config
($vmid, $conf);
1631 lock_container
($vmid, 10, $updatefn);
1633 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $conf->{'pve.volid'}, $snapname);
1635 lock_container
($vmid, 5, $unlockfn);