10 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
14 use PVE
::JSONSchema
qw(get_standard_option);
15 use PVE
::Tools
qw($IPV6RE $IPV4RE);
20 my $nodename = PVE
::INotify
::nodename
();
22 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
24 PVE
::JSONSchema
::register_format
('pve-lxc-network', \
&verify_lxc_network
);
25 sub verify_lxc_network
{
26 my ($value, $noerr) = @_;
28 return $value if parse_lxc_network
($value);
30 return undef if $noerr;
32 die "unable to parse network setting\n";
35 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', \
&verify_ct_mountpoint
);
36 sub verify_ct_mountpoint
{
37 my ($value, $noerr) = @_;
39 return $value if parse_ct_mountpoint
($value);
41 return undef if $noerr;
43 die "unable to parse CT mountpoint options\n";
46 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
47 type
=> 'string', format
=> 'pve-ct-mountpoint',
48 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
49 description
=> "Use volume as container root. You can use special '<storage>:<size>' syntax for create/restore, where size specifies the disk size in GB (for example 'local:5.5' to create 5.5GB image on storage 'local').",
57 description
=> "Lock/unlock the VM.",
58 enum
=> [qw(migrate backup snapshot rollback)],
63 description
=> "Specifies whether a VM will be started during system bootup.",
66 startup
=> get_standard_option
('pve-startup-order'),
70 enum
=> ['amd64', 'i386'],
71 description
=> "OS architecture type.",
77 enum
=> ['debian', 'ubuntu', 'centos'],
78 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
83 description
=> "Specify the number of tty available to the container",
91 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.",
99 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.",
107 description
=> "Amount of RAM for the VM in MB.",
114 description
=> "Amount of SWAP for the VM in MB.",
120 description
=> "Set a host name for the container.",
127 description
=> "Container description. Only used on the configuration web interface.",
132 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
137 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.",
139 rootfs
=> get_standard_option
('pve-ct-rootfs'),
142 type
=> 'string', format
=> 'pve-configid',
144 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
148 description
=> "Timestamp for snapshots.",
154 my $valid_lxc_conf_keys = {
158 'lxc.haltsignal' => 1,
159 'lxc.rebootsignal' => 1,
160 'lxc.stopsignal' => 1,
162 'lxc.network.type' => 1,
163 'lxc.network.flags' => 1,
164 'lxc.network.link' => 1,
165 'lxc.network.mtu' => 1,
166 'lxc.network.name' => 1,
167 'lxc.network.hwaddr' => 1,
168 'lxc.network.ipv4' => 1,
169 'lxc.network.ipv4.gateway' => 1,
170 'lxc.network.ipv6' => 1,
171 'lxc.network.ipv6.gateway' => 1,
172 'lxc.network.script.up' => 1,
173 'lxc.network.script.down' => 1,
175 'lxc.console.logfile' => 1,
178 'lxc.devttydir' => 1,
179 'lxc.hook.autodev' => 1,
183 'lxc.mount.entry' => 1,
184 'lxc.mount.auto' => 1,
186 'lxc.rootfs.mount' => 1,
187 'lxc.rootfs.options' => 1,
191 'lxc.aa_profile' => 1,
192 'lxc.aa_allow_incomplete' => 1,
193 'lxc.se_context' => 1,
196 'lxc.hook.pre-start' => 1,
197 'lxc.hook.pre-mount' => 1,
198 'lxc.hook.mount' => 1,
199 'lxc.hook.start' => 1,
200 'lxc.hook.post-stop' => 1,
201 'lxc.hook.clone' => 1,
202 'lxc.hook.destroy' => 1,
205 'lxc.start.auto' => 1,
206 'lxc.start.delay' => 1,
207 'lxc.start.order' => 1,
209 'lxc.environment' => 1,
216 my $MAX_LXC_NETWORKS = 10;
217 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
218 $confdesc->{"net$i"} = {
220 type
=> 'string', format
=> 'pve-lxc-network',
221 description
=> "Specifies network interfaces for the container.\n\n".
222 "The string should have the follow format:\n\n".
223 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
224 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
225 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
226 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
230 sub write_pct_config
{
231 my ($filename, $conf) = @_;
233 delete $conf->{snapstate
}; # just to be sure
235 my $generate_raw_config = sub {
240 # add description as comment to top of file
241 my $descr = $conf->{description
} || '';
242 foreach my $cl (split(/\n/, $descr)) {
243 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
246 foreach my $key (sort keys %$conf) {
247 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
248 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
249 $raw .= "$key: $conf->{$key}\n";
252 if (my $lxcconf = $conf->{lxc
}) {
253 foreach my $entry (@$lxcconf) {
254 my ($k, $v) = @$entry;
262 my $raw = &$generate_raw_config($conf);
264 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
265 $raw .= "\n[$snapname]\n";
266 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
273 my ($key, $value) = @_;
275 die "unknown setting '$key'\n" if !$confdesc->{$key};
277 my $type = $confdesc->{$key}->{type
};
279 if (!defined($value)) {
280 die "got undefined value\n";
283 if ($value =~ m/[\n\r]/) {
284 die "property contains a line feed\n";
287 if ($type eq 'boolean') {
288 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
289 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
290 die "type check ('boolean') failed - got '$value'\n";
291 } elsif ($type eq 'integer') {
292 return int($1) if $value =~ m/^(\d+)$/;
293 die "type check ('integer') failed - got '$value'\n";
294 } elsif ($type eq 'number') {
295 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
296 die "type check ('number') failed - got '$value'\n";
297 } elsif ($type eq 'string') {
298 if (my $fmt = $confdesc->{$key}->{format
}) {
299 PVE
::JSONSchema
::check_format
($fmt, $value);
308 sub parse_pct_config
{
309 my ($filename, $raw) = @_;
311 return undef if !defined($raw);
314 digest
=> Digest
::SHA
::sha1_hex
($raw),
318 $filename =~ m
|/lxc/(\d
+).conf
$|
319 || die "got strange filename '$filename'";
327 my @lines = split(/\n/, $raw);
328 foreach my $line (@lines) {
329 next if $line =~ m/^\s*$/;
331 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
333 $conf->{description
} = $descr if $descr;
335 $conf = $res->{snapshots
}->{$section} = {};
339 if ($line =~ m/^\#(.*)\s*$/) {
340 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
344 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(\S.*)\s*$/) {
347 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
348 push @{$conf->{lxc
}}, [$key, $value];
350 warn "vm $vmid - unable to parse config: $line\n";
352 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
353 $descr .= PVE
::Tools
::decode_text
($2);
354 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
355 $conf->{snapstate
} = $1;
356 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
359 eval { $value = check_type
($key, $value); };
360 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
361 $conf->{$key} = $value;
363 warn "vm $vmid - unable to parse config: $line\n";
367 $conf->{description
} = $descr if $descr;
369 delete $res->{snapstate
}; # just to be sure
375 my $vmlist = PVE
::Cluster
::get_vmlist
();
377 return $res if !$vmlist || !$vmlist->{ids
};
378 my $ids = $vmlist->{ids
};
380 foreach my $vmid (keys %$ids) {
381 next if !$vmid; # skip CT0
382 my $d = $ids->{$vmid};
383 next if !$d->{node
} || $d->{node
} ne $nodename;
384 next if !$d->{type
} || $d->{type
} ne 'lxc';
385 $res->{$vmid}->{type
} = 'lxc';
390 sub cfs_config_path
{
391 my ($vmid, $node) = @_;
393 $node = $nodename if !$node;
394 return "nodes/$node/lxc/$vmid.conf";
398 my ($vmid, $node) = @_;
400 my $cfspath = cfs_config_path
($vmid, $node);
401 return "/etc/pve/$cfspath";
407 my $cfspath = cfs_config_path
($vmid);
409 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
410 die "container $vmid does not exists\n" if !defined($conf);
416 my ($vmid, $conf) = @_;
418 my $dir = "/etc/pve/nodes/$nodename/lxc";
421 write_config
($vmid, $conf);
427 unlink config_file
($vmid, $nodename);
431 my ($vmid, $conf) = @_;
433 my $cfspath = cfs_config_path
($vmid);
435 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
438 # flock: we use one file handle per process, so lock file
439 # can be called multiple times and succeeds for the same process.
441 my $lock_handles = {};
442 my $lockdir = "/run/lock/lxc";
447 return "$lockdir/pve-config-{$vmid}.lock";
451 my ($vmid, $timeout) = @_;
453 $timeout = 10 if !$timeout;
456 my $filename = lock_filename
($vmid);
458 mkdir $lockdir if !-d
$lockdir;
460 my $lock_func = sub {
461 if (!$lock_handles->{$$}->{$filename}) {
462 my $fh = new IO
::File
(">>$filename") ||
463 die "can't open file - $!\n";
464 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
467 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
468 print STDERR
"trying to aquire lock...";
471 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
472 # try again on EINTR (see bug #273)
473 if ($success || ($! != EINTR
)) {
478 print STDERR
" failed\n";
479 die "can't aquire lock - $!\n";
482 $lock_handles->{$$}->{$filename}->{refcount
}++;
484 print STDERR
" OK\n";
488 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
491 die "can't lock file '$filename' - $err";
498 my $filename = lock_filename
($vmid);
500 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
501 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
502 if ($refcount <= 0) {
503 $lock_handles->{$$}->{$filename} = undef;
510 my ($vmid, $timeout, $code, @param) = @_;
514 lock_aquire
($vmid, $timeout);
515 eval { $res = &$code(@param) };
527 return defined($confdesc->{$name});
530 # add JSON properties for create and set function
531 sub json_config_properties
{
534 foreach my $opt (keys %$confdesc) {
535 next if $opt eq 'parent' || $opt eq 'snaptime';
536 next if $prop->{$opt};
537 $prop->{$opt} = $confdesc->{$opt};
543 sub json_config_properties_no_rootfs
{
546 foreach my $opt (keys %$confdesc) {
547 next if $prop->{$opt};
548 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
549 $prop->{$opt} = $confdesc->{$opt};
555 # container status helpers
557 sub list_active_containers
{
559 my $filename = "/proc/net/unix";
561 # similar test is used by lcxcontainers.c: list_active_containers
564 my $fh = IO
::File-
>new ($filename, "r");
567 while (defined(my $line = <$fh>)) {
568 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
570 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
581 # warning: this is slow
585 my $active_hash = list_active_containers
();
587 return 1 if defined($active_hash->{$vmid});
592 sub get_container_disk_usage
{
595 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
605 if (my ($fsid, $total, $used, $avail) = $line =~
606 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
614 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
623 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
625 my $active_hash = list_active_containers
();
627 foreach my $vmid (keys %$list) {
628 my $d = $list->{$vmid};
630 my $running = defined($active_hash->{$vmid});
632 $d->{status
} = $running ?
'running' : 'stopped';
634 my $cfspath = cfs_config_path
($vmid);
635 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
637 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
638 $d->{name
} =~ s/[\s]//g;
640 $d->{cpus
} = $conf->{cpulimit
} // 0;
643 my $res = get_container_disk_usage
($vmid);
644 $d->{disk
} = $res->{used
};
645 $d->{maxdisk
} = $res->{total
};
648 # use 4GB by default ??
649 if (my $rootfs = $conf->{rootfs
}) {
650 my $rootinfo = parse_ct_mountpoint
($rootfs);
651 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
653 $d->{maxdisk
} = 4*1024*1024*1024;
659 $d->{maxmem
} = $conf->{memory
}*1024*1024;
660 $d->{maxswap
} = $conf->{swap
}*1024*1024;
672 foreach my $vmid (keys %$list) {
673 my $d = $list->{$vmid};
674 next if $d->{status
} ne 'running';
676 $d->{uptime
} = 100; # fixme:
678 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
679 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
681 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
682 my @bytes = split(/\n/, $blkio_bytes);
683 foreach my $byte (@bytes) {
684 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
685 $d->{diskread
} = $2 if $key eq 'Read';
686 $d->{diskwrite
} = $2 if $key eq 'Write';
694 my $parse_size = sub {
697 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
698 my ($size, $unit) = ($1, $3);
701 $size = $size * 1024;
702 } elsif ($unit eq 'M') {
703 $size = $size * 1024 * 1024;
704 } elsif ($unit eq 'G') {
705 $size = $size * 1024 * 1024 * 1024;
711 sub parse_ct_mountpoint
{
718 foreach my $p (split (/,/, $data)) {
719 next if $p =~ m/^\s*$/;
721 if ($p =~ m/^(volume|backup|size)=(.+)$/) {
722 my ($k, $v) = ($1, $2);
723 return undef if defined($res->{$k});
725 if (!$res->{volume
} && $p !~ m/=/) {
733 return undef if !$res->{volume
};
735 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
738 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
744 sub print_lxc_network
{
747 die "no network name defined\n" if !$net->{name
};
749 my $res = "name=$net->{name}";
751 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
752 next if !defined($net->{$k});
753 $res .= ",$k=$net->{$k}";
759 sub parse_lxc_network
{
764 return $res if !$data;
766 foreach my $pv (split (/,/, $data)) {
767 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
774 $res->{type
} = 'veth';
775 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
780 sub read_cgroup_value
{
781 my ($group, $vmid, $name, $full) = @_;
783 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
785 return PVE
::Tools
::file_get_contents
($path) if $full;
787 return PVE
::Tools
::file_read_firstline
($path);
790 sub write_cgroup_value
{
791 my ($group, $vmid, $name, $value) = @_;
793 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
794 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
798 sub find_lxc_console_pids
{
802 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
805 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
808 my @args = split(/\0/, $cmdline);
810 # serach for lxc-console -n <vmid>
811 return if scalar(@args) != 3;
812 return if $args[1] ne '-n';
813 return if $args[2] !~ m/^\d+$/;
814 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
818 push @{$res->{$vmid}}, $pid;
830 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
832 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
834 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
839 my $ipv4_reverse_mask = [
875 # Note: we cannot use Net:IP, because that only allows strict
877 sub parse_ipv4_cidr
{
878 my ($cidr, $noerr) = @_;
880 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
881 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
884 return undef if $noerr;
886 die "unable to parse ipv4 address/mask\n";
892 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
895 sub update_lxc_config
{
896 my ($storage_cfg, $vmid, $conf) = @_;
900 die "missing 'arch' - internal error" if !$conf->{arch
};
901 $raw .= "lxc.arch = $conf->{arch}\n";
903 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
904 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
905 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
910 my $ttycount = $conf->{tty
} // 4;
911 $raw .= "lxc.tty = $ttycount\n";
913 my $utsname = $conf->{hostname
} || "CT$vmid";
914 $raw .= "lxc.utsname = $utsname\n";
916 my $memory = $conf->{memory
} || 512;
917 my $swap = $conf->{swap
} // 0;
919 my $lxcmem = int($memory*1024*1024);
920 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
922 my $lxcswap = int(($memory + $swap)*1024*1024);
923 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
925 if (my $cpulimit = $conf->{cpulimit
}) {
926 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
927 my $value = int(100000*$cpulimit);
928 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
931 my $shares = $conf->{cpuunits
} || 1024;
932 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
934 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
935 my $volid = $rootinfo->{volume
};
936 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
938 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
939 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
940 my $rootfs = PVE
::Storage
::path
($storage_cfg, $volid);
941 $raw .= "lxc.rootfs = loop:$rootfs\n";
942 } elsif ($scfg->{type
} eq 'zfspool') {
943 my $rootfs = PVE
::Storage
::path
($storage_cfg, $volid);
944 $raw .= "lxc.rootfs = $rootfs\n";
945 } elsif ($scfg->{type
} eq 'drbd') {
946 my $rootdev = PVE
::Storage
::path
($storage_cfg, $volid);
947 $raw .= "lxc.rootfs = $rootdev\n";
949 die "unsupported storage type '$scfg->{type}'\n";
953 foreach my $k (keys %$conf) {
954 next if $k !~ m/^net(\d+)$/;
956 my $d = parse_lxc_network
($conf->{$k});
958 $raw .= "lxc.network.type = veth\n";
959 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
960 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
961 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
962 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
965 if (my $lxcconf = $conf->{lxc
}) {
966 foreach my $entry (@$lxcconf) {
967 my ($k, $v) = @$entry;
968 $netcount++ if $k eq 'lxc.network.type';
973 $raw .= "lxc.network.type = empty\n" if !$netcount;
975 my $dir = "/var/lib/lxc/$vmid";
976 File
::Path
::mkpath
("$dir/rootfs");
978 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
981 # verify and cleanup nameserver list (replace \0 with ' ')
982 sub verify_nameserver_list
{
983 my ($nameserver_list) = @_;
986 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
987 PVE
::JSONSchema
::pve_verify_ip
($server);
991 return join(' ', @list);
994 sub verify_searchdomain_list
{
995 my ($searchdomain_list) = @_;
998 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
999 # todo: should we add checks for valid dns domains?
1000 push @list, $server;
1003 return join(' ', @list);
1006 sub update_pct_config
{
1007 my ($vmid, $conf, $running, $param, $delete) = @_;
1013 my $pid = find_lxc_pid
($vmid);
1014 $rootdir = "/proc/$pid/root";
1017 if (defined($delete)) {
1018 foreach my $opt (@$delete) {
1019 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1020 die "unable to delete required option '$opt'\n";
1021 } elsif ($opt eq 'swap') {
1022 delete $conf->{$opt};
1023 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1024 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1025 delete $conf->{$opt};
1026 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain') {
1027 delete $conf->{$opt};
1028 push @nohotplug, $opt;
1030 } elsif ($opt =~ m/^net(\d)$/) {
1031 delete $conf->{$opt};
1034 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1038 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1042 # There's no separate swap size to configure, there's memory and "total"
1043 # memory (iow. memory+swap). This means we have to change them together.
1044 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1045 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1046 if (defined($wanted_memory) || defined($wanted_swap)) {
1048 $wanted_memory //= ($conf->{memory
} || 512);
1049 $wanted_swap //= ($conf->{swap
} || 0);
1051 my $total = $wanted_memory + $wanted_swap;
1053 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1054 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1056 $conf->{memory
} = $wanted_memory;
1057 $conf->{swap
} = $wanted_swap;
1059 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1062 foreach my $opt (keys %$param) {
1063 my $value = $param->{$opt};
1064 if ($opt eq 'hostname') {
1065 $conf->{$opt} = $value;
1066 } elsif ($opt eq 'onboot') {
1067 $conf->{$opt} = $value ?
1 : 0;
1068 } elsif ($opt eq 'startup') {
1069 $conf->{$opt} = $value;
1070 } elsif ($opt eq 'tty') {
1071 $conf->{$opt} = $value;
1072 push @nohotplug, $opt;
1074 } elsif ($opt eq 'nameserver') {
1075 my $list = verify_nameserver_list
($value);
1076 $conf->{$opt} = $list;
1077 push @nohotplug, $opt;
1079 } elsif ($opt eq 'searchdomain') {
1080 my $list = verify_searchdomain_list
($value);
1081 $conf->{$opt} = $list;
1082 push @nohotplug, $opt;
1084 } elsif ($opt eq 'cpulimit') {
1085 $conf->{$opt} = $value;
1086 push @nohotplug, $opt; # fixme: hotplug
1088 } elsif ($opt eq 'cpuunits') {
1089 $conf->{$opt} = $value;
1090 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1091 } elsif ($opt eq 'description') {
1092 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1093 } elsif ($opt =~ m/^net(\d+)$/) {
1095 my $net = parse_lxc_network
($value);
1097 $conf->{$opt} = print_lxc_network
($net);
1099 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1102 die "implement me: $opt";
1104 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1107 if ($running && scalar(@nohotplug)) {
1108 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1112 sub get_primary_ips
{
1115 # return data from net0
1117 return undef if !defined($conf->{net0
});
1118 my $net = parse_lxc_network
($conf->{net0
});
1120 my $ipv4 = $net->{ip
};
1122 if ($ipv4 =~ /^(dhcp|manual)$/) {
1128 my $ipv6 = $net->{ip6
};
1130 if ($ipv6 =~ /^(dhcp|manual)$/) {
1137 return ($ipv4, $ipv6);
1140 sub destroy_lxc_container
{
1141 my ($storage_cfg, $vmid, $conf) = @_;
1143 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1144 if (defined($rootinfo->{volume
})) {
1145 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1146 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1148 rmdir "/var/lib/lxc/$vmid/rootfs";
1149 unlink "/var/lib/lxc/$vmid/config";
1150 rmdir "/var/lib/lxc/$vmid";
1151 destroy_config
($vmid);
1153 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1154 #PVE::Tools::run_command($cmd);
1157 my $safe_num_ne = sub {
1160 return 0 if !defined($a) && !defined($b);
1161 return 1 if !defined($a);
1162 return 1 if !defined($b);
1167 my $safe_string_ne = sub {
1170 return 0 if !defined($a) && !defined($b);
1171 return 1 if !defined($a);
1172 return 1 if !defined($b);
1178 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1180 if ($newnet->{type
} ne 'veth') {
1181 # for when there are physical interfaces
1182 die "cannot update interface of type $newnet->{type}";
1185 my $veth = "veth${vmid}i${netid}";
1186 my $eth = $newnet->{name
};
1188 if (my $oldnetcfg = $conf->{$opt}) {
1189 my $oldnet = parse_lxc_network
($oldnetcfg);
1191 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1192 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1194 PVE
::Network
::veth_delete
($veth);
1195 delete $conf->{$opt};
1196 PVE
::LXC
::write_config
($vmid, $conf);
1198 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1200 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1201 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1202 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1204 if ($oldnet->{bridge
}) {
1205 PVE
::Network
::tap_unplug
($veth);
1206 foreach (qw(bridge tag firewall)) {
1207 delete $oldnet->{$_};
1209 $conf->{$opt} = print_lxc_network
($oldnet);
1210 PVE
::LXC
::write_config
($vmid, $conf);
1213 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1214 foreach (qw(bridge tag firewall)) {
1215 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1217 $conf->{$opt} = print_lxc_network
($oldnet);
1218 PVE
::LXC
::write_config
($vmid, $conf);
1221 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1224 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1228 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1230 my $veth = "veth${vmid}i${netid}";
1231 my $vethpeer = $veth . "p";
1232 my $eth = $newnet->{name
};
1234 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1235 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1237 # attach peer in container
1238 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1239 PVE
::Tools
::run_command
($cmd);
1241 # link up peer in container
1242 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1243 PVE
::Tools
::run_command
($cmd);
1245 my $done = { type
=> 'veth' };
1246 foreach (qw(bridge tag firewall hwaddr name)) {
1247 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1249 $conf->{$opt} = print_lxc_network
($done);
1251 PVE
::LXC
::write_config
($vmid, $conf);
1254 sub update_ipconfig
{
1255 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1257 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1259 my $optdata = parse_lxc_network
($conf->{$opt});
1263 my $cmdargs = shift;
1264 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1266 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1268 my $change_ip_config = sub {
1269 my ($ipversion) = @_;
1271 my $family_opt = "-$ipversion";
1272 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1273 my $gw= "gw$suffix";
1274 my $ip= "ip$suffix";
1276 my $newip = $newnet->{$ip};
1277 my $newgw = $newnet->{$gw};
1278 my $oldip = $optdata->{$ip};
1280 my $change_ip = &$safe_string_ne($oldip, $newip);
1281 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1283 return if !$change_ip && !$change_gw;
1285 # step 1: add new IP, if this fails we cancel
1286 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1287 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1294 # step 2: replace gateway
1295 # If this fails we delete the added IP and cancel.
1296 # If it succeeds we save the config and delete the old IP, ignoring
1297 # errors. The config is then saved.
1298 # Note: 'ip route replace' can add
1301 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1304 # the route was not replaced, the old IP is still available
1305 # rollback (delete new IP) and cancel
1307 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1308 warn $@ if $@; # no need to die here
1313 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1314 # if the route was not deleted, the guest might have deleted it manually
1320 # from this point on we save the configuration
1321 # step 3: delete old IP ignoring errors
1322 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1323 # We need to enable promote_secondaries, otherwise our newly added
1324 # address will be removed along with the old one.
1327 if ($ipversion == 4) {
1328 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1329 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1330 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1332 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1334 warn $@ if $@; # no need to die here
1336 if ($ipversion == 4) {
1337 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1341 foreach my $property ($ip, $gw) {
1342 if ($newnet->{$property}) {
1343 $optdata->{$property} = $newnet->{$property};
1345 delete $optdata->{$property};
1348 $conf->{$opt} = print_lxc_network
($optdata);
1349 PVE
::LXC
::write_config
($vmid, $conf);
1350 $lxc_setup->setup_network($conf);
1353 &$change_ip_config(4);
1354 &$change_ip_config(6);
1358 # Internal snapshots
1360 # NOTE: Snapshot create/delete involves several non-atomic
1361 # action, and can take a long time.
1362 # So we try to avoid locking the file and use 'lock' variable
1363 # inside the config file instead.
1365 my $snapshot_copy_config = sub {
1366 my ($source, $dest) = @_;
1368 foreach my $k (keys %$source) {
1369 next if $k eq 'snapshots';
1370 next if $k eq 'snapstate';
1371 next if $k eq 'snaptime';
1372 next if $k eq 'vmstate';
1373 next if $k eq 'lock';
1374 next if $k eq 'digest';
1375 next if $k eq 'description';
1377 $dest->{$k} = $source->{$k};
1381 my $snapshot_prepare = sub {
1382 my ($vmid, $snapname, $comment) = @_;
1386 my $updatefn = sub {
1388 my $conf = load_config
($vmid);
1392 $conf->{lock} = 'snapshot';
1394 die "snapshot name '$snapname' already used\n"
1395 if defined($conf->{snapshots
}->{$snapname});
1397 my $storecfg = PVE
::Storage
::config
();
1398 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1400 $snap = $conf->{snapshots
}->{$snapname} = {};
1402 &$snapshot_copy_config($conf, $snap);
1404 $snap->{'snapstate'} = "prepare";
1405 $snap->{'snaptime'} = time();
1406 $snap->{'description'} = $comment if $comment;
1407 $conf->{snapshots
}->{$snapname} = $snap;
1409 PVE
::LXC
::write_config
($vmid, $conf);
1412 lock_container
($vmid, 10, $updatefn);
1417 my $snapshot_commit = sub {
1418 my ($vmid, $snapname) = @_;
1420 my $updatefn = sub {
1422 my $conf = load_config
($vmid);
1424 die "missing snapshot lock\n"
1425 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1427 die "snapshot '$snapname' does not exist\n"
1428 if !defined($conf->{snapshots
}->{$snapname});
1430 die "wrong snapshot state\n"
1431 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1432 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1434 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1435 delete $conf->{lock};
1436 $conf->{parent
} = $snapname;
1438 PVE
::LXC
::write_config
($vmid, $conf);
1441 lock_container
($vmid, 10 ,$updatefn);
1445 my ($feature, $conf, $storecfg, $snapname) = @_;
1447 #Fixme add other drives if necessary.
1450 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1451 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1453 return $err ?
0 : 1;
1456 sub snapshot_create
{
1457 my ($vmid, $snapname, $comment) = @_;
1459 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1461 my $conf = load_config
($vmid);
1463 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1464 my $running = check_running
($vmid);
1467 PVE
::Tools
::run_command
($cmd);
1470 my $storecfg = PVE
::Storage
::config
();
1471 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1472 my $volid = $rootinfo->{volume
};
1474 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1476 PVE
::Tools
::run_command
($cmd);
1479 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1480 &$snapshot_commit($vmid, $snapname);
1483 snapshot_delete
($vmid, $snapname, 1);
1488 sub snapshot_delete
{
1489 my ($vmid, $snapname, $force) = @_;
1495 my $updatefn = sub {
1497 $conf = load_config
($vmid);
1499 $snap = $conf->{snapshots
}->{$snapname};
1503 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1505 $snap->{snapstate
} = 'delete';
1507 PVE
::LXC
::write_config
($vmid, $conf);
1510 lock_container
($vmid, 10, $updatefn);
1512 my $storecfg = PVE
::Storage
::config
();
1514 my $del_snap = sub {
1518 if ($conf->{parent
} eq $snapname) {
1519 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1520 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1522 delete $conf->{parent
};
1526 delete $conf->{snapshots
}->{$snapname};
1528 PVE
::LXC
::write_config
($vmid, $conf);
1531 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1532 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1533 my $volid = $rootinfo->{volume
};
1536 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1540 if(!$err || ($err && $force)) {
1541 lock_container
($vmid, 10, $del_snap);
1543 die "Can't delete snapshot: $vmid $snapname $err\n";
1548 sub snapshot_rollback
{
1549 my ($vmid, $snapname) = @_;
1551 my $storecfg = PVE
::Storage
::config
();
1553 my $conf = load_config
($vmid);
1555 my $snap = $conf->{snapshots
}->{$snapname};
1557 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1559 my $rootfs = $snap->{rootfs
};
1560 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1561 my $volid = $rootinfo->{volume
};
1563 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1565 my $updatefn = sub {
1567 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1568 if $snap->{snapstate
};
1572 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1574 die "unable to rollback vm $vmid: vm is running\n"
1575 if check_running
($vmid);
1577 $conf->{lock} = 'rollback';
1581 # copy snapshot config to current config
1583 my $tmp_conf = $conf;
1584 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1585 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1586 delete $conf->{snaptime
};
1587 delete $conf->{snapname
};
1588 $conf->{parent
} = $snapname;
1590 PVE
::LXC
::write_config
($vmid, $conf);
1593 my $unlockfn = sub {
1594 delete $conf->{lock};
1595 PVE
::LXC
::write_config
($vmid, $conf);
1598 lock_container
($vmid, 10, $updatefn);
1600 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1602 lock_container
($vmid, 5, $unlockfn);