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,
76 'lxc.haltsignal' => 1,
77 'lxc.rebootsignal' => 1,
78 'lxc.stopsignal' => 1,
81 'lxc.console.logfile' => 1,
87 'lxc.aa_profile' => 1,
88 'lxc.aa_allow_incomplete' => 1,
89 'lxc.se_context' => 1,
92 'lxc.environment' => 1,
93 'lxc.cgroup.devices.deny' => 1,
96 'lxc.start.auto' => 1,
97 'lxc.start.delay' => 1,
98 'lxc.start.order' => 1,
102 'lxc.hook.pre-start' => 1,
103 'lxc.hook.pre-mount' => 1,
104 'lxc.hook.mount' => 1,
105 'lxc.hook.autodev' => 1,
106 'lxc.hook.start' => 1,
107 'lxc.hook.post-stop' => 1,
108 'lxc.hook.clone' => 1,
111 'pve.nameserver' => sub {
112 my ($name, $value) = @_;
113 return verify_nameserver_list
($value);
115 'pve.searchdomain' => sub {
116 my ($name, $value) = @_;
117 return verify_searchdomain_list
($value);
119 'pve.onboot' => '(0|1)',
120 'pve.startup' => sub {
121 my ($name, $value) = @_;
122 return PVE
::JSONSchema
::pve_verify_startup_order
($value);
125 'pve.disksize' => '\d+(\.\d+)?',
127 my ($name, $value) = @_;
128 PVE
::Storage
::parse_volume_id
($value);
135 'pve.snapcomment' => 1,
137 'pve.snapstate' => 1,
141 my $valid_lxc_network_keys = {
144 name
=> 1, # ifname inside container
145 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
149 my $valid_pve_network_keys = {
159 my $lxc_array_configs = {
164 'lxc.cgroup.devices.deny' => 1,
167 sub write_lxc_config
{
168 my ($filename, $data) = @_;
172 return $raw if !$data;
174 my $dump_entry = sub {
175 my ($k, $value, $done_hash, $snapshot) = @_;
176 return if !defined($value);
177 return if $done_hash->{$k};
178 $done_hash->{$k} = 1;
180 die "got unexpected reference for '$k'"
181 if !$lxc_array_configs->{$k};
182 foreach my $v (@$value) {
183 $raw .= 'snap.' if $snapshot;
187 $raw .= 'snap.' if $snapshot;
188 $raw .= "$k = $value\n";
192 my $config_writer = sub {
193 my ($elem, $snapshot) = @_;
195 my $done_hash = { digest
=> 1};
197 if (defined(my $value = $elem->{'pve.snapname'})) {
198 &$dump_entry('pve.snapname', $value, $done_hash, $snapshot);
201 # Note: Order is important! Include defaults first, so that we
202 # can overwrite them later.
203 &$dump_entry('lxc.include', $elem->{'lxc.include'}, $done_hash, $snapshot);
205 foreach my $k (sort keys %$elem) {
206 next if $k !~ m/^lxc\./;
207 &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
209 foreach my $k (sort keys %$elem) {
210 next if $k !~ m/^pve\./;
211 &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
213 my $network_count = 0;
215 foreach my $k (sort keys %$elem) {
216 next if $k !~ m/^net\d+$/;
217 $done_hash->{$k} = 1;
219 my $net = $elem->{$k};
221 $raw .= 'snap.' if $snapshot;
222 $raw .= "lxc.network.type = $net->{type}\n";
223 foreach my $subkey (sort keys %$net) {
224 next if $subkey eq 'type';
225 if ($valid_lxc_network_keys->{$subkey}) {
226 $raw .= 'snap.' if $snapshot;
227 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
228 } elsif ($valid_pve_network_keys->{$subkey}) {
229 $raw .= 'snap.' if $snapshot;
230 $raw .= "pve.network.$subkey = $net->{$subkey}\n";
232 die "found invalid network key '$subkey'";
236 if (!$network_count) {
237 $raw .= 'snap.' if $snapshot;
238 $raw .= "lxc.network.type = empty\n";
240 foreach my $k (sort keys %$elem) {
241 next if $k eq 'snapshots';
242 next if $done_hash->{$k};
243 die "found un-written value \"$k\" in config
- implement this
!";
248 &$config_writer($data);
250 if ($data->{snapshots}) {
251 my @tmp = sort { $data->{snapshots}->{$b}{'pve.snaptime'} <=>
252 $data->{snapshots}->{$a}{'pve.snaptime'} }
253 keys %{$data->{snapshots}};
254 foreach my $snapname (@tmp) {
256 &$config_writer($data->{snapshots}->{$snapname}, 1);
263 sub parse_lxc_option {
264 my ($name, $value) = @_;
266 my $parser = $valid_lxc_keys->{$name};
268 die "invalid key
'$name'\n" if !defined($parser);
270 if ($parser eq '1') {
272 } elsif (ref($parser)) {
273 my $res = &$parser($name, $value);
274 return $res if defined($res);
277 return $value if $value =~ m/^$parser$/;
280 die "unable to parse value
'$value' for option
'$name'\n";
283 sub parse_lxc_config {
284 my ($filename, $raw) = @_;
286 return undef if !defined($raw);
289 digest => Digest::SHA::sha1_hex($raw),
292 $filename =~ m|/lxc/(\d+)/config$|
293 || die "got strange filename
'$filename'";
298 my $network_counter = 0;
299 my $network_list = [];
300 my $host_ifnames = {};
304 my $find_next_hostif_name = sub {
305 for (my $i = 0; $i < 10; $i++) {
306 my $name = "veth
${vmid
}.$i";
307 if (!$host_ifnames->{$name}) {
308 $host_ifnames->{$name} = 1;
313 die "unable to find free host_ifname
"; # should not happen
316 my $push_network = sub {
319 push @{$network_list}, $netconf;
321 if (my $netname = $netconf->{'veth.pair'}) {
322 if ($netname =~ m/^veth(\d+).(\d)$/) {
323 die "wrong vmid
for network interface pair
\n" if $1 != $vmid;
324 my $host_ifnames->{$netname} = 1;
326 die "wrong network interface pair
\n";
331 my $finalize_section = sub {
332 &$push_network($network); # flush
334 foreach my $net (@{$network_list}) {
335 next if $net->{type} eq 'empty'; # skip
336 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
337 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
338 die "unsupported network type
'$net->{type}'\n" if $net->{type} ne 'veth';
340 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
342 $data->{snapshots}->{$snapname}->{"net
$1"} = $net;
344 $data->{"net
$1"} = $net;
350 $network_counter = 0;
356 while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
358 next if $line =~ m/^\s*$/; # skip empty lines
359 next if $line =~ m/^#/; # skip comments
361 # snap.pve.snapname starts new sections
362 if ($line =~ m/^(snap\.)?pve\.snapname\s*=\s*(\w*)\s*$/) {
365 &$finalize_section();
368 $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
370 } elsif ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
371 my ($subkey, $value) = ($2, $3);
372 if ($subkey eq 'type') {
373 &$push_network($network);
374 $network = { type => $value };
375 } elsif ($valid_lxc_network_keys->{$subkey}) {
376 $network->{$subkey} = $value;
378 die "unable to parse config line
: $line\n";
380 } elsif ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
381 my ($subkey, $value) = ($2, $3);
382 if ($valid_pve_network_keys->{$subkey}) {
383 $network->{$subkey} = $value;
385 die "unable to parse config line
: $line\n";
387 } elsif ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
388 my ($name, $value) = ($2, $3);
390 if ($lxc_array_configs->{$name}) {
391 $data->{$name} = [] if !defined($data->{$name});
393 push @{$data->{snapshots}->{$snapname}->{$name}}, parse_lxc_option($name, $value);
395 push @{$data->{$name}}, parse_lxc_option($name, $value);
399 die "multiple definitions
for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
400 $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
402 die "multiple definitions
for $name\n" if defined($data->{$name});
403 $data->{$name} = parse_lxc_option($name, $value);
407 die "unable to parse config line
: $line\n";
411 &$finalize_section();
417 my $vmlist = PVE::Cluster::get_vmlist();
419 return $res if !$vmlist || !$vmlist->{ids};
420 my $ids = $vmlist->{ids};
422 foreach my $vmid (keys %$ids) {
423 next if !$vmid; # skip CT0
424 my $d = $ids->{$vmid};
425 next if !$d->{node} || $d->{node} ne $nodename;
426 next if !$d->{type} || $d->{type} ne 'lxc';
427 $res->{$vmid}->{type} = 'lxc';
432 sub cfs_config_path {
433 my ($vmid, $node) = @_;
435 $node = $nodename if !$node;
436 return "nodes
/$node/lxc/$vmid/config";
440 my ($vmid, $node) = @_;
442 my $cfspath = cfs_config_path($vmid, $node);
443 return "/etc/pve
/$cfspath";
449 my $cfspath = cfs_config_path($vmid);
451 my $conf = PVE::Cluster::cfs_read_file($cfspath);
452 die "container
$vmid does not exists\n" if !defined($conf);
458 my ($vmid, $conf) = @_;
460 my $dir = "/etc/pve
/nodes/$nodename/lxc";
464 mkdir($dir) || die "unable to create container configuration directory
- $!\n";
466 write_config($vmid, $conf);
472 my $dir = "/etc/pve
/nodes/$nodename/lxc/$vmid";
473 File::Path::rmtree($dir);
477 my ($vmid, $conf) = @_;
479 my $cfspath = cfs_config_path($vmid);
481 PVE::Cluster::cfs_write_file($cfspath, $conf);
485 sub write_temp_config {
486 my ($vmid, $conf) = @_;
489 my $filename = "/tmp/temp-lxc-conf-
$vmid-$$-$tempcounter.conf
";
491 my $raw = write_lxc_config($filename, $conf);
493 PVE::Tools::file_set_contents($filename, $raw);
498 # flock: we use one file handle per process, so lock file
499 # can be called multiple times and succeeds for the same process.
501 my $lock_handles = {};
502 my $lockdir = "/run/lock
/lxc
";
507 return "$lockdir/pve-config
-{$vmid}.lock";
511 my ($vmid, $timeout) = @_;
513 $timeout = 10 if !$timeout;
516 my $filename = lock_filename($vmid);
518 mkdir $lockdir if !-d $lockdir;
520 my $lock_func = sub {
521 if (!$lock_handles->{$$}->{$filename}) {
522 my $fh = new IO::File(">>$filename") ||
523 die "can
't open file - $!\n";
524 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
527 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
528 print STDERR "trying to aquire lock...";
531 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
532 # try again on EINTR (see bug #273)
533 if ($success || ($! != EINTR)) {
538 print STDERR " failed\n";
539 die "can't aquire
lock - $!\n";
542 $lock_handles->{$$}->{$filename}->{refcount}++;
544 print STDERR " OK
\n";
548 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
551 die "can
't lock file '$filename' - $err";
558 my $filename = lock_filename($vmid);
560 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
561 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
562 if ($refcount <= 0) {
563 $lock_handles->{$$}->{$filename} = undef;
570 my ($vmid, $timeout, $code, @param) = @_;
574 lock_aquire($vmid, $timeout);
575 eval { $res = &$code(@param) };
588 description => "Specifies whether a VM will be started during system bootup.",
591 startup => get_standard_option('pve-startup-order
'),
595 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.",
603 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.",
611 description => "Amount of RAM for the VM in MB.",
618 description => "Amount of SWAP for the VM in MB.",
625 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
631 description => "Set a host name for the container.",
638 description => "Container description. Only used on the configuration web interface.",
643 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
648 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.",
652 my $MAX_LXC_NETWORKS = 10;
653 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
654 $confdesc->{"net$i"} = {
656 type => 'string
', format => 'pve-lxc-network
',
657 description => "Specifies network interfaces for the container.\n\n".
658 "The string should have the follow format:\n\n".
659 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
660 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
661 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
662 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
669 return defined($confdesc->{$name});
672 # add JSON properties for create and set function
673 sub json_config_properties {
676 foreach my $opt (keys %$confdesc) {
677 $prop->{$opt} = $confdesc->{$opt};
683 # container status helpers
685 sub list_active_containers {
687 my $filename = "/proc/net/unix";
689 # similar test is used by lcxcontainers.c: list_active_containers
692 my $fh = IO::File->new ($filename, "r");
695 while (defined(my $line = <$fh>)) {
696 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
698 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
709 # warning: this is slow
713 my $active_hash = list_active_containers();
715 return 1 if defined($active_hash->{$vmid});
720 sub get_container_disk_usage {
723 my $cmd = ['lxc-attach
', '-n
', $vmid, '--', 'df
', '-P
', '-B
', '1', '/'];
733 if (my ($fsid, $total, $used, $avail) = $line =~
734 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
742 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
751 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc
' }} : config_list();
753 my $active_hash = list_active_containers();
755 foreach my $vmid (keys %$list) {
756 my $d = $list->{$vmid};
758 my $running = defined($active_hash->{$vmid});
760 $d->{status} = $running ? 'running
' : 'stopped
';
762 my $cfspath = cfs_config_path($vmid);
763 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
765 $d->{name} = $conf->{'lxc
.utsname
'} || "CT$vmid";
766 $d->{name} =~ s/[\s]//g;
770 my $cfs_period_us = $conf->{'lxc
.cgroup
.cpu
.cfs_period_us
'};
771 my $cfs_quota_us = $conf->{'lxc
.cgroup
.cpu
.cfs_quota_us
'};
773 if ($cfs_period_us && $cfs_quota_us) {
774 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
778 $d->{maxdisk} = defined($conf->{'pve
.disksize
'}) ?
779 int($conf->{'pve
.disksize
'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
781 if (my $private = $conf->{'lxc
.rootfs
'}) {
782 if ($private =~ m!^/!) {
783 my $res = PVE::Tools::df($private, 2);
784 $d->{disk} = $res->{used};
785 $d->{maxdisk} = $res->{total};
787 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
788 my $res = get_container_disk_usage($vmid);
789 $d->{disk} = $res->{used};
790 $d->{maxdisk} = $res->{total};
797 $d->{maxmem} = ($conf->{'lxc
.cgroup
.memory
.limit_in_bytes
'}||0) +
798 ($conf->{'lxc
.cgroup
.memory
.memsw
.limit_in_bytes
'}||0);
810 foreach my $vmid (keys %$list) {
811 my $d = $list->{$vmid};
812 next if $d->{status} ne 'running
';
814 $d->{uptime} = 100; # fixme:
816 $d->{mem} = read_cgroup_value('memory
', $vmid, 'memory
.usage_in_bytes
');
817 $d->{swap} = read_cgroup_value('memory
', $vmid, 'memory
.memsw
.usage_in_bytes
') - $d->{mem};
819 my $blkio_bytes = read_cgroup_value('blkio
', $vmid, 'blkio
.throttle
.io_service_bytes
', 1);
820 my @bytes = split(/\n/, $blkio_bytes);
821 foreach my $byte (@bytes) {
822 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
823 $d->{diskread} = $2 if $key eq 'Read
';
824 $d->{diskwrite} = $2 if $key eq 'Write
';
833 sub print_lxc_network {
836 die "no network name defined\n" if !$net->{name};
838 my $res = "name=$net->{name}";
840 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
841 next if !defined($net->{$k});
842 $res .= ",$k=$net->{$k}";
848 sub parse_lxc_network
{
853 return $res if !$data;
855 foreach my $pv (split (/,/, $data)) {
856 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
863 $res->{type
} = 'veth';
864 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
869 sub read_cgroup_value
{
870 my ($group, $vmid, $name, $full) = @_;
872 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
874 return PVE
::Tools
::file_get_contents
($path) if $full;
876 return PVE
::Tools
::file_read_firstline
($path);
879 sub write_cgroup_value
{
880 my ($group, $vmid, $name, $value) = @_;
882 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
883 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
887 sub find_lxc_console_pids
{
891 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
894 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
897 my @args = split(/\0/, $cmdline);
899 # serach for lxc-console -n <vmid>
900 return if scalar(@args) != 3;
901 return if $args[1] ne '-n';
902 return if $args[2] !~ m/^\d+$/;
903 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
907 push @{$res->{$vmid}}, $pid;
919 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
921 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
923 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
928 my $ipv4_reverse_mask = [
964 # Note: we cannot use Net:IP, because that only allows strict
966 sub parse_ipv4_cidr
{
967 my ($cidr, $noerr) = @_;
969 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
970 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
973 return undef if $noerr;
975 die "unable to parse ipv4 address/mask\n";
981 die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
984 sub lxc_conf_to_pve
{
985 my ($vmid, $lxc_conf) = @_;
987 my $properties = json_config_properties
();
989 my $conf = { digest
=> $lxc_conf->{digest
} };
991 foreach my $k (keys %$properties) {
993 if ($k eq 'description') {
994 if (my $raw = $lxc_conf->{'pve.comment'}) {
995 $conf->{$k} = PVE
::Tools
::decode_text
($raw);
997 } elsif ($k eq 'onboot') {
998 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
999 } elsif ($k eq 'startup') {
1000 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
1001 } elsif ($k eq 'hostname') {
1002 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
1003 } elsif ($k eq 'nameserver') {
1004 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
1005 } elsif ($k eq 'searchdomain') {
1006 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
1007 } elsif ($k eq 'memory') {
1008 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
1009 $conf->{$k} = int($value / (1024*1024));
1011 } elsif ($k eq 'swap') {
1012 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
1013 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
1014 $conf->{$k} = int(($value -$mem) / (1024*1024));
1016 } elsif ($k eq 'cpulimit') {
1017 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
1018 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1020 if ($cfs_period_us && $cfs_quota_us) {
1021 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
1025 } elsif ($k eq 'cpuunits') {
1026 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
1027 } elsif ($k eq 'disk') {
1028 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
1029 $lxc_conf->{'pve.disksize'} : 0;
1030 } elsif ($k =~ m/^net\d$/) {
1031 my $net = $lxc_conf->{$k};
1033 $conf->{$k} = print_lxc_network
($net);
1037 if (my $parent = $lxc_conf->{'pve.parent'}) {
1038 $conf->{parent
} = $lxc_conf->{'pve.parent'};
1041 if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
1042 $conf->{description
} = $lxc_conf->{'pve.snapcomment'};
1045 if (my $parent = $lxc_conf->{'pve.snaptime'}) {
1046 $conf->{snaptime
} = $lxc_conf->{'pve.snaptime'};
1052 # verify and cleanup nameserver list (replace \0 with ' ')
1053 sub verify_nameserver_list
{
1054 my ($nameserver_list) = @_;
1057 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1058 PVE
::JSONSchema
::pve_verify_ip
($server);
1059 push @list, $server;
1062 return join(' ', @list);
1065 sub verify_searchdomain_list
{
1066 my ($searchdomain_list) = @_;
1069 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1070 # todo: should we add checks for valid dns domains?
1071 push @list, $server;
1074 return join(' ', @list);
1077 sub update_lxc_config
{
1078 my ($vmid, $conf, $running, $param, $delete) = @_;
1084 my $pid = find_lxc_pid
($vmid);
1085 $rootdir = "/proc/$pid/root";
1088 if (defined($delete)) {
1089 foreach my $opt (@$delete) {
1090 if ($opt eq 'hostname' || $opt eq 'memory') {
1091 die "unable to delete required option '$opt'\n";
1092 } elsif ($opt eq 'swap') {
1093 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
1094 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1095 } elsif ($opt eq 'description') {
1096 delete $conf->{'pve.comment'};
1097 } elsif ($opt eq 'onboot') {
1098 delete $conf->{'pve.onboot'};
1099 } elsif ($opt eq 'startup') {
1100 delete $conf->{'pve.startup'};
1101 } elsif ($opt eq 'nameserver') {
1102 delete $conf->{'pve.nameserver'};
1103 push @nohotplug, $opt;
1105 } elsif ($opt eq 'searchdomain') {
1106 delete $conf->{'pve.searchdomain'};
1107 push @nohotplug, $opt;
1109 } elsif ($opt =~ m/^net(\d)$/) {
1110 delete $conf->{$opt};
1113 PVE
::Network
::veth_delete
("veth${vmid}.$netid");
1117 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1121 foreach my $opt (keys %$param) {
1122 my $value = $param->{$opt};
1123 if ($opt eq 'hostname') {
1124 $conf->{'lxc.utsname'} = $value;
1125 } elsif ($opt eq 'onboot') {
1126 $conf->{'pve.onboot'} = $value ?
1 : 0;
1127 } elsif ($opt eq 'startup') {
1128 $conf->{'pve.startup'} = $value;
1129 } elsif ($opt eq 'nameserver') {
1130 my $list = verify_nameserver_list
($value);
1131 $conf->{'pve.nameserver'} = $list;
1132 push @nohotplug, $opt;
1134 } elsif ($opt eq 'searchdomain') {
1135 my $list = verify_searchdomain_list
($value);
1136 $conf->{'pve.searchdomain'} = $list;
1137 push @nohotplug, $opt;
1139 } elsif ($opt eq 'memory') {
1140 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
1141 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", $value*1024*1024);
1142 } elsif ($opt eq 'swap') {
1143 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
1144 $mem = $param->{memory
}*1024*1024 if $param->{memory
};
1145 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
1146 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", $mem + $value*1024*1024);
1148 } elsif ($opt eq 'cpulimit') {
1150 my $cfs_period_us = 100000;
1151 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
1152 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
1153 write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", $cfs_period_us*$value);
1155 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1156 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1157 write_cgroup_value
("cpu", $vmid, "cpu.cfs_quota_us", -1);
1159 } elsif ($opt eq 'cpuunits') {
1160 $conf->{'lxc.cgroup.cpu.shares'} = $value;
1161 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1162 } elsif ($opt eq 'description') {
1163 $conf->{'pve.comment'} = PVE
::Tools
::encode_text
($value);
1164 } elsif ($opt eq 'disk') {
1165 $conf->{'pve.disksize'} = $value;
1166 push @nohotplug, $opt;
1168 } elsif ($opt =~ m/^net(\d+)$/) {
1170 my $net = PVE
::LXC
::parse_lxc_network
($value);
1171 $net->{'veth.pair'} = "veth${vmid}.$netid";
1173 $conf->{$opt} = $net;
1175 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1178 die "implement me: $opt";
1180 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1183 if ($running && scalar(@nohotplug)) {
1184 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1188 sub get_primary_ips
{
1191 # return data from net0
1193 my $net = $conf->{net0
};
1194 return undef if !$net;
1196 my $ipv4 = $net->{ip
};
1198 if ($ipv4 =~ /^(dhcp|manual)$/) {
1204 my $ipv6 = $net->{ip6
};
1206 if ($ipv6 =~ /^(dhcp|manual)$/) {
1213 return ($ipv4, $ipv6);
1216 sub destory_lxc_container
{
1217 my ($storage_cfg, $vmid, $conf) = @_;
1219 if (my $volid = $conf->{'pve.volid'}) {
1221 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1222 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1224 PVE
::Storage
::vdisk_free
($storage_cfg, $volid);
1226 destroy_config
($vmid);
1229 my $cmd = ['lxc-destroy', '-n', $vmid ];
1231 PVE
::Tools
::run_command
($cmd);
1235 my $safe_num_ne = sub {
1238 return 0 if !defined($a) && !defined($b);
1239 return 1 if !defined($a);
1240 return 1 if !defined($b);
1245 my $safe_string_ne = sub {
1248 return 0 if !defined($a) && !defined($b);
1249 return 1 if !defined($a);
1250 return 1 if !defined($b);
1256 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1258 my $veth = $newnet->{'veth.pair'};
1259 my $vethpeer = $veth . "p";
1260 my $eth = $newnet->{name
};
1262 if ($conf->{$opt}) {
1263 if (&$safe_string_ne($conf->{$opt}->{hwaddr
}, $newnet->{hwaddr
}) ||
1264 &$safe_string_ne($conf->{$opt}->{name
}, $newnet->{name
})) {
1266 PVE
::Network
::veth_delete
($veth);
1267 delete $conf->{$opt};
1268 PVE
::LXC
::write_config
($vmid, $conf);
1270 hotplug_net
($vmid, $conf, $opt, $newnet);
1272 } elsif (&$safe_string_ne($conf->{$opt}->{bridge
}, $newnet->{bridge
}) ||
1273 &$safe_num_ne($conf->{$opt}->{tag
}, $newnet->{tag
}) ||
1274 &$safe_num_ne($conf->{$opt}->{firewall
}, $newnet->{firewall
})) {
1276 if ($conf->{$opt}->{bridge
}){
1277 PVE
::Network
::tap_unplug
($veth);
1278 delete $conf->{$opt}->{bridge
};
1279 delete $conf->{$opt}->{tag
};
1280 delete $conf->{$opt}->{firewall
};
1281 PVE
::LXC
::write_config
($vmid, $conf);
1284 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1285 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1286 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1287 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1288 PVE
::LXC
::write_config
($vmid, $conf);
1291 hotplug_net
($vmid, $conf, $opt, $newnet);
1294 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1298 my ($vmid, $conf, $opt, $newnet) = @_;
1300 my $veth = $newnet->{'veth.pair'};
1301 my $vethpeer = $veth . "p";
1302 my $eth = $newnet->{name
};
1304 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1305 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1307 # attach peer in container
1308 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1309 PVE
::Tools
::run_command
($cmd);
1311 # link up peer in container
1312 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1313 PVE
::Tools
::run_command
($cmd);
1315 $conf->{$opt}->{type
} = 'veth';
1316 $conf->{$opt}->{bridge
} = $newnet->{bridge
} if $newnet->{bridge
};
1317 $conf->{$opt}->{tag
} = $newnet->{tag
} if $newnet->{tag
};
1318 $conf->{$opt}->{firewall
} = $newnet->{firewall
} if $newnet->{firewall
};
1319 $conf->{$opt}->{hwaddr
} = $newnet->{hwaddr
} if $newnet->{hwaddr
};
1320 $conf->{$opt}->{name
} = $newnet->{name
} if $newnet->{name
};
1321 $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
1323 delete $conf->{$opt}->{ip
} if $conf->{$opt}->{ip
};
1324 delete $conf->{$opt}->{ip6
} if $conf->{$opt}->{ip6
};
1325 delete $conf->{$opt}->{gw
} if $conf->{$opt}->{gw
};
1326 delete $conf->{$opt}->{gw6
} if $conf->{$opt}->{gw6
};
1328 PVE
::LXC
::write_config
($vmid, $conf);
1331 sub update_ipconfig
{
1332 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1334 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1336 my $optdata = $conf->{$opt};
1340 my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
1341 PVE
::Tools
::run_command
($cmd);
1344 my $change_ip_config = sub {
1345 my ($ipversion) = @_;
1347 my $family_opt = "-$ipversion";
1348 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1349 my $gw= "gw$suffix";
1350 my $ip= "ip$suffix";
1352 my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
1353 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
1355 return if !$change_ip && !$change_gw;
1357 # step 1: add new IP, if this fails we cancel
1358 if ($change_ip && $newnet->{$ip}) {
1359 eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
1366 # step 2: replace gateway
1367 # If this fails we delete the added IP and cancel.
1368 # If it succeeds we save the config and delete the old IP, ignoring
1369 # errors. The config is then saved.
1370 # Note: 'ip route replace' can add
1372 if ($newnet->{$gw}) {
1373 eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
1376 # the route was not replaced, the old IP is still available
1377 # rollback (delete new IP) and cancel
1379 eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
1380 warn $@ if $@; # no need to die here
1385 eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
1386 # if the route was not deleted, the guest might have deleted it manually
1392 # from this point on we safe the configuration
1393 # step 3: delete old IP ignoring errors
1394 if ($change_ip && $optdata->{$ip}) {
1395 eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
1396 warn $@ if $@; # no need to die here
1399 foreach my $property ($ip, $gw) {
1400 if ($newnet->{$property}) {
1401 $optdata->{$property} = $newnet->{$property};
1403 delete $optdata->{$property};
1406 PVE
::LXC
::write_config
($vmid, $conf);
1407 $lxc_setup->setup_network($conf);
1410 &$change_ip_config(4);
1411 &$change_ip_config(6);
1415 # Internal snapshots
1417 # NOTE: Snapshot create/delete involves several non-atomic
1418 # action, and can take a long time.
1419 # So we try to avoid locking the file and use 'lock' variable
1420 # inside the config file instead.
1422 my $snapshot_copy_config = sub {
1423 my ($source, $dest) = @_;
1425 foreach my $k (keys %$source) {
1426 next if $k eq 'snapshots';
1427 next if $k eq 'pve.snapstate';
1428 next if $k eq 'pve.snaptime';
1429 next if $k eq 'pve.lock';
1430 next if $k eq 'digest';
1431 next if $k eq 'pve.comment';
1433 $dest->{$k} = $source->{$k};
1437 my $snapshot_prepare = sub {
1438 my ($vmid, $snapname, $comment) = @_;
1442 my $updatefn = sub {
1444 my $conf = load_config
($vmid);
1448 $conf->{'pve.lock'} = 'snapshot';
1450 die "snapshot name '$snapname' already used\n"
1451 if defined($conf->{snapshots
}->{$snapname});
1453 my $storecfg = PVE
::Storage
::config
();
1454 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1456 $snap = $conf->{snapshots
}->{$snapname} = {};
1458 &$snapshot_copy_config($conf, $snap);
1460 $snap->{'pve.snapstate'} = "prepare";
1461 $snap->{'pve.snaptime'} = time();
1462 $snap->{'pve.snapname'} = $snapname;
1463 $snap->{'pve.snapcomment'} = $comment if $comment;
1464 $conf->{snapshots
}->{$snapname} = $snap;
1466 PVE
::LXC
::write_config
($vmid, $conf);
1469 lock_container
($vmid, 10, $updatefn);
1474 my $snapshot_commit = sub {
1475 my ($vmid, $snapname) = @_;
1477 my $updatefn = sub {
1479 my $conf = load_config
($vmid);
1481 die "missing snapshot lock\n"
1482 if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
1484 die "snapshot '$snapname' does not exist\n"
1485 if !defined($conf->{snapshots
}->{$snapname});
1487 die "wrong snapshot state\n"
1488 if !($conf->{snapshots
}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots
}->{$snapname}->{'pve.snapstate'} eq "prepare");
1490 delete $conf->{snapshots
}->{$snapname}->{'pve.snapstate'};
1491 delete $conf->{'pve.lock'};
1492 $conf->{'pve.parent'} = $snapname;
1494 PVE
::LXC
::write_config
($vmid, $conf);
1498 lock_container
($vmid, 10 ,$updatefn);
1502 my ($feature, $conf, $storecfg, $snapname) = @_;
1503 #Fixme add other drives if necessary.
1505 my $volid = $conf->{'pve.volid'};
1506 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $volid, $snapname);
1508 return $err ?
0 : 1;
1511 sub snapshot_create
{
1512 my ($vmid, $snapname, $comment) = @_;
1514 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1516 my $config = load_config
($vmid);
1518 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1519 my $running = check_running
($vmid);
1522 PVE
::Tools
::run_command
($cmd);
1525 my $storecfg = PVE
::Storage
::config
();
1526 my $volid = $config->{'pve.volid'};
1528 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1530 PVE
::Tools
::run_command
($cmd);
1533 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1534 &$snapshot_commit($vmid, $snapname);
1537 snapshot_delete
($vmid, $snapname, 1);
1542 sub snapshot_delete
{
1543 my ($vmid, $snapname, $force) = @_;
1549 my $updatefn = sub {
1551 $conf = load_config
($vmid);
1553 $snap = $conf->{snapshots
}->{$snapname};
1557 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1559 $snap->{'pve.snapstate'} = 'delete';
1561 PVE
::LXC
::write_config
($vmid, $conf);
1564 lock_container
($vmid, 10, $updatefn);
1566 my $storecfg = PVE
::Storage
::config
();
1568 my $del_snap = sub {
1572 if ($conf->{'pve.parent'} eq $snapname) {
1573 if ($conf->{snapshots
}->{$snapname}->{'pve.snapname'}) {
1574 $conf->{'pve.parent'} = $conf->{snapshots
}->{$snapname}->{'pve.parent'};
1576 delete $conf->{'pve.parent'};
1580 delete $conf->{snapshots
}->{$snapname};
1582 PVE
::LXC
::write_config
($vmid, $conf);
1585 my $volid = $conf->{snapshots
}->{$snapname}->{'pve.volid'};
1588 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1592 if(!$err || ($err && $force)) {
1593 lock_container
($vmid, 10, $del_snap);
1595 die "Can't delete snapshot: $vmid $snapname $err\n";
1600 sub snapshot_rollback
{
1601 my ($vmid, $snapname) = @_;
1603 my $storecfg = PVE
::Storage
::config
();
1605 my $conf = load_config
($vmid);
1607 my $snap = $conf->{snapshots
}->{$snapname};
1609 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1611 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $snap->{'pve.volid'},
1614 my $updatefn = sub {
1616 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" if $snap->{snapstate
};
1620 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1622 die "unable to rollback vm $vmid: vm is running\n"
1623 if check_running
($vmid);
1625 $conf->{'pve.lock'} = 'rollback';
1629 # copy snapshot config to current config
1631 my $tmp_conf = $conf;
1632 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1633 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1634 delete $conf->{'pve.snaptime'};
1635 delete $conf->{'pve.snapname'};
1636 $conf->{'pve.parent'} = $snapname;
1638 PVE
::LXC
::write_config
($vmid, $conf);
1641 my $unlockfn = sub {
1642 delete $conf->{'pve.lock'};
1643 PVE
::LXC
::write_config
($vmid, $conf);
1646 lock_container
($vmid, 10, $updatefn);
1648 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $conf->{'pve.volid'}, $snapname);
1650 lock_container
($vmid, 5, $unlockfn);