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.",
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 description
=> "Enable/disable Template.",
76 enum
=> ['amd64', 'i386'],
77 description
=> "OS architecture type.",
83 enum
=> ['debian', 'ubuntu', 'centos'],
84 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
89 description
=> "Attach a console device (/dev/console) to the container.",
95 description
=> "Specify the number of tty available to the container",
103 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.",
111 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.",
119 description
=> "Amount of RAM for the VM in MB.",
126 description
=> "Amount of SWAP for the VM in MB.",
132 description
=> "Set a host name for the container.",
139 description
=> "Container description. Only used on the configuration web interface.",
144 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
149 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.",
151 rootfs
=> get_standard_option
('pve-ct-rootfs'),
154 type
=> 'string', format
=> 'pve-configid',
156 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
160 description
=> "Timestamp for snapshots.",
166 my $valid_lxc_conf_keys = {
170 'lxc.haltsignal' => 1,
171 'lxc.rebootsignal' => 1,
172 'lxc.stopsignal' => 1,
174 'lxc.network.type' => 1,
175 'lxc.network.flags' => 1,
176 'lxc.network.link' => 1,
177 'lxc.network.mtu' => 1,
178 'lxc.network.name' => 1,
179 'lxc.network.hwaddr' => 1,
180 'lxc.network.ipv4' => 1,
181 'lxc.network.ipv4.gateway' => 1,
182 'lxc.network.ipv6' => 1,
183 'lxc.network.ipv6.gateway' => 1,
184 'lxc.network.script.up' => 1,
185 'lxc.network.script.down' => 1,
187 'lxc.console.logfile' => 1,
190 'lxc.devttydir' => 1,
191 'lxc.hook.autodev' => 1,
195 'lxc.mount.entry' => 1,
196 'lxc.mount.auto' => 1,
198 'lxc.rootfs.mount' => 1,
199 'lxc.rootfs.options' => 1,
203 'lxc.aa_profile' => 1,
204 'lxc.aa_allow_incomplete' => 1,
205 'lxc.se_context' => 1,
208 'lxc.hook.pre-start' => 1,
209 'lxc.hook.pre-mount' => 1,
210 'lxc.hook.mount' => 1,
211 'lxc.hook.start' => 1,
212 'lxc.hook.post-stop' => 1,
213 'lxc.hook.clone' => 1,
214 'lxc.hook.destroy' => 1,
217 'lxc.start.auto' => 1,
218 'lxc.start.delay' => 1,
219 'lxc.start.order' => 1,
221 'lxc.environment' => 1,
228 my $MAX_LXC_NETWORKS = 10;
229 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
230 $confdesc->{"net$i"} = {
232 type
=> 'string', format
=> 'pve-lxc-network',
233 description
=> "Specifies network interfaces for the container.\n\n".
234 "The string should have the follow format:\n\n".
235 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
236 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
237 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
238 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
242 my $MAX_MOUNT_POINTS = 10;
243 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
244 $confdesc->{"mp$i"} = {
246 type
=> 'string', format
=> 'pve-ct-mountpoint',
247 typetext
=> '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
248 description
=> "Use volume as container mount point.",
253 sub write_pct_config
{
254 my ($filename, $conf) = @_;
256 delete $conf->{snapstate
}; # just to be sure
258 my $generate_raw_config = sub {
263 # add description as comment to top of file
264 my $descr = $conf->{description
} || '';
265 foreach my $cl (split(/\n/, $descr)) {
266 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
269 foreach my $key (sort keys %$conf) {
270 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
271 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
272 $raw .= "$key: $conf->{$key}\n";
275 if (my $lxcconf = $conf->{lxc
}) {
276 foreach my $entry (@$lxcconf) {
277 my ($k, $v) = @$entry;
285 my $raw = &$generate_raw_config($conf);
287 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
288 $raw .= "\n[$snapname]\n";
289 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
296 my ($key, $value) = @_;
298 die "unknown setting '$key'\n" if !$confdesc->{$key};
300 my $type = $confdesc->{$key}->{type
};
302 if (!defined($value)) {
303 die "got undefined value\n";
306 if ($value =~ m/[\n\r]/) {
307 die "property contains a line feed\n";
310 if ($type eq 'boolean') {
311 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
312 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
313 die "type check ('boolean') failed - got '$value'\n";
314 } elsif ($type eq 'integer') {
315 return int($1) if $value =~ m/^(\d+)$/;
316 die "type check ('integer') failed - got '$value'\n";
317 } elsif ($type eq 'number') {
318 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
319 die "type check ('number') failed - got '$value'\n";
320 } elsif ($type eq 'string') {
321 if (my $fmt = $confdesc->{$key}->{format
}) {
322 PVE
::JSONSchema
::check_format
($fmt, $value);
331 sub parse_pct_config
{
332 my ($filename, $raw) = @_;
334 return undef if !defined($raw);
337 digest
=> Digest
::SHA
::sha1_hex
($raw),
341 $filename =~ m
|/lxc/(\d
+).conf
$|
342 || die "got strange filename '$filename'";
350 my @lines = split(/\n/, $raw);
351 foreach my $line (@lines) {
352 next if $line =~ m/^\s*$/;
354 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
356 $conf->{description
} = $descr if $descr;
358 $conf = $res->{snapshots
}->{$section} = {};
362 if ($line =~ m/^\#(.*)\s*$/) {
363 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
367 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
370 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
371 push @{$conf->{lxc
}}, [$key, $value];
373 warn "vm $vmid - unable to parse config: $line\n";
375 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
376 $descr .= PVE
::Tools
::decode_text
($2);
377 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
378 $conf->{snapstate
} = $1;
379 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
382 eval { $value = check_type
($key, $value); };
383 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
384 $conf->{$key} = $value;
386 warn "vm $vmid - unable to parse config: $line\n";
390 $conf->{description
} = $descr if $descr;
392 delete $res->{snapstate
}; # just to be sure
398 my $vmlist = PVE
::Cluster
::get_vmlist
();
400 return $res if !$vmlist || !$vmlist->{ids
};
401 my $ids = $vmlist->{ids
};
403 foreach my $vmid (keys %$ids) {
404 next if !$vmid; # skip CT0
405 my $d = $ids->{$vmid};
406 next if !$d->{node
} || $d->{node
} ne $nodename;
407 next if !$d->{type
} || $d->{type
} ne 'lxc';
408 $res->{$vmid}->{type
} = 'lxc';
413 sub cfs_config_path
{
414 my ($vmid, $node) = @_;
416 $node = $nodename if !$node;
417 return "nodes/$node/lxc/$vmid.conf";
421 my ($vmid, $node) = @_;
423 my $cfspath = cfs_config_path
($vmid, $node);
424 return "/etc/pve/$cfspath";
430 my $cfspath = cfs_config_path
($vmid);
432 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
433 die "container $vmid does not exists\n" if !defined($conf);
439 my ($vmid, $conf) = @_;
441 my $dir = "/etc/pve/nodes/$nodename/lxc";
444 write_config
($vmid, $conf);
450 unlink config_file
($vmid, $nodename);
454 my ($vmid, $conf) = @_;
456 my $cfspath = cfs_config_path
($vmid);
458 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
461 # flock: we use one file handle per process, so lock file
462 # can be called multiple times and succeeds for the same process.
464 my $lock_handles = {};
465 my $lockdir = "/run/lock/lxc";
470 return "$lockdir/pve-config-{$vmid}.lock";
474 my ($vmid, $timeout) = @_;
476 $timeout = 10 if !$timeout;
479 my $filename = lock_filename
($vmid);
481 mkdir $lockdir if !-d
$lockdir;
483 my $lock_func = sub {
484 if (!$lock_handles->{$$}->{$filename}) {
485 my $fh = new IO
::File
(">>$filename") ||
486 die "can't open file - $!\n";
487 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
490 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
491 print STDERR
"trying to aquire lock...";
494 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
495 # try again on EINTR (see bug #273)
496 if ($success || ($! != EINTR
)) {
501 print STDERR
" failed\n";
502 die "can't aquire lock - $!\n";
505 $lock_handles->{$$}->{$filename}->{refcount
}++;
507 print STDERR
" OK\n";
511 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
514 die "can't lock file '$filename' - $err";
521 my $filename = lock_filename
($vmid);
523 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
524 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
525 if ($refcount <= 0) {
526 $lock_handles->{$$}->{$filename} = undef;
533 my ($vmid, $timeout, $code, @param) = @_;
537 lock_aquire
($vmid, $timeout);
538 eval { $res = &$code(@param) };
550 return defined($confdesc->{$name});
553 # add JSON properties for create and set function
554 sub json_config_properties
{
557 foreach my $opt (keys %$confdesc) {
558 next if $opt eq 'parent' || $opt eq 'snaptime';
559 next if $prop->{$opt};
560 $prop->{$opt} = $confdesc->{$opt};
566 sub json_config_properties_no_rootfs
{
569 foreach my $opt (keys %$confdesc) {
570 next if $prop->{$opt};
571 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
572 $prop->{$opt} = $confdesc->{$opt};
578 # container status helpers
580 sub list_active_containers
{
582 my $filename = "/proc/net/unix";
584 # similar test is used by lcxcontainers.c: list_active_containers
587 my $fh = IO
::File-
>new ($filename, "r");
590 while (defined(my $line = <$fh>)) {
591 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
593 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
604 # warning: this is slow
608 my $active_hash = list_active_containers
();
610 return 1 if defined($active_hash->{$vmid});
615 sub get_container_disk_usage
{
618 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
628 if (my ($fsid, $total, $used, $avail) = $line =~
629 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
637 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
646 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
648 my $active_hash = list_active_containers
();
650 foreach my $vmid (keys %$list) {
651 my $d = $list->{$vmid};
653 my $running = defined($active_hash->{$vmid});
655 $d->{status
} = $running ?
'running' : 'stopped';
657 my $cfspath = cfs_config_path
($vmid);
658 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
660 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
661 $d->{name
} =~ s/[\s]//g;
663 $d->{cpus
} = $conf->{cpulimit
} // 0;
666 my $res = get_container_disk_usage
($vmid);
667 $d->{disk
} = $res->{used
};
668 $d->{maxdisk
} = $res->{total
};
671 # use 4GB by default ??
672 if (my $rootfs = $conf->{rootfs
}) {
673 my $rootinfo = parse_ct_mountpoint
($rootfs);
674 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
676 $d->{maxdisk
} = 4*1024*1024*1024;
682 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
683 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
694 $d->{template
} = is_template
($conf);
697 foreach my $vmid (keys %$list) {
698 my $d = $list->{$vmid};
699 next if $d->{status
} ne 'running';
701 $d->{uptime
} = 100; # fixme:
703 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
704 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
706 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
707 my @bytes = split(/\n/, $blkio_bytes);
708 foreach my $byte (@bytes) {
709 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
710 $d->{diskread
} = $2 if $key eq 'Read';
711 $d->{diskwrite
} = $2 if $key eq 'Write';
719 my $parse_size = sub {
722 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
723 my ($size, $unit) = ($1, $3);
726 $size = $size * 1024;
727 } elsif ($unit eq 'M') {
728 $size = $size * 1024 * 1024;
729 } elsif ($unit eq 'G') {
730 $size = $size * 1024 * 1024 * 1024;
736 sub parse_ct_mountpoint
{
743 foreach my $p (split (/,/, $data)) {
744 next if $p =~ m/^\s*$/;
746 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
747 my ($k, $v) = ($1, $2);
748 return undef if defined($res->{$k});
751 if (!$res->{volume
} && $p !~ m/=/) {
759 return undef if !$res->{volume
};
761 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
764 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
770 sub print_ct_mountpoint
{
775 die "missing volume\n" if !$info->{volume
};
777 foreach my $o ('size', 'backup') {
778 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
781 return "$info->{volume}$opts";
784 sub print_lxc_network
{
787 die "no network name defined\n" if !$net->{name
};
789 my $res = "name=$net->{name}";
791 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
792 next if !defined($net->{$k});
793 $res .= ",$k=$net->{$k}";
799 sub parse_lxc_network
{
804 return $res if !$data;
806 foreach my $pv (split (/,/, $data)) {
807 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
814 $res->{type
} = 'veth';
815 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
820 sub read_cgroup_value
{
821 my ($group, $vmid, $name, $full) = @_;
823 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
825 return PVE
::Tools
::file_get_contents
($path) if $full;
827 return PVE
::Tools
::file_read_firstline
($path);
830 sub write_cgroup_value
{
831 my ($group, $vmid, $name, $value) = @_;
833 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
834 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
838 sub find_lxc_console_pids
{
842 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
845 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
848 my @args = split(/\0/, $cmdline);
850 # serach for lxc-console -n <vmid>
851 return if scalar(@args) != 3;
852 return if $args[1] ne '-n';
853 return if $args[2] !~ m/^\d+$/;
854 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
858 push @{$res->{$vmid}}, $pid;
870 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
872 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
874 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
879 my $ipv4_reverse_mask = [
915 # Note: we cannot use Net:IP, because that only allows strict
917 sub parse_ipv4_cidr
{
918 my ($cidr, $noerr) = @_;
920 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
921 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
924 return undef if $noerr;
926 die "unable to parse ipv4 address/mask\n";
932 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
935 sub update_lxc_config
{
936 my ($storage_cfg, $vmid, $conf) = @_;
938 my $dir = "/var/lib/lxc/$vmid";
940 if ($conf->{template
}) {
942 unlink "$dir/config";
949 die "missing 'arch' - internal error" if !$conf->{arch
};
950 $raw .= "lxc.arch = $conf->{arch}\n";
952 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
953 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
954 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
959 if (!has_dev_console
($conf)) {
960 $raw .= "lxc.console = none\n";
961 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
964 my $ttycount = get_tty_count
($conf);
965 $raw .= "lxc.tty = $ttycount\n";
967 my $utsname = $conf->{hostname
} || "CT$vmid";
968 $raw .= "lxc.utsname = $utsname\n";
970 my $memory = $conf->{memory
} || 512;
971 my $swap = $conf->{swap
} // 0;
973 my $lxcmem = int($memory*1024*1024);
974 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
976 my $lxcswap = int(($memory + $swap)*1024*1024);
977 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
979 if (my $cpulimit = $conf->{cpulimit
}) {
980 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
981 my $value = int(100000*$cpulimit);
982 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
985 my $shares = $conf->{cpuunits
} || 1024;
986 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
988 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
989 my $volid = $rootinfo->{volume
};
990 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
992 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
993 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
995 die "unable to use template as rootfs\n" if $isBase;
997 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
998 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
1000 if ($format eq 'subvol') {
1001 $raw .= "lxc.rootfs = $path\n";
1002 } elsif ($format eq 'raw') {
1003 if ($scfg->{path
}) {
1004 $raw .= "lxc.rootfs = loop:$path\n";
1005 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
1006 $raw .= "lxc.rootfs = $path\n";
1008 die "unsupported storage type '$scfg->{type}'\n";
1011 die "unsupported image format '$format'\n";
1015 foreach my $k (keys %$conf) {
1016 next if $k !~ m/^net(\d+)$/;
1018 my $d = parse_lxc_network
($conf->{$k});
1020 $raw .= "lxc.network.type = veth\n";
1021 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1022 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1023 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1024 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1027 if (my $lxcconf = $conf->{lxc
}) {
1028 foreach my $entry (@$lxcconf) {
1029 my ($k, $v) = @$entry;
1030 $netcount++ if $k eq 'lxc.network.type';
1031 $raw .= "$k = $v\n";
1035 $raw .= "lxc.network.type = empty\n" if !$netcount;
1037 File
::Path
::mkpath
("$dir/rootfs");
1039 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1042 # verify and cleanup nameserver list (replace \0 with ' ')
1043 sub verify_nameserver_list
{
1044 my ($nameserver_list) = @_;
1047 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1048 PVE
::JSONSchema
::pve_verify_ip
($server);
1049 push @list, $server;
1052 return join(' ', @list);
1055 sub verify_searchdomain_list
{
1056 my ($searchdomain_list) = @_;
1059 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1060 # todo: should we add checks for valid dns domains?
1061 push @list, $server;
1064 return join(' ', @list);
1067 sub update_pct_config
{
1068 my ($vmid, $conf, $running, $param, $delete) = @_;
1074 my $pid = find_lxc_pid
($vmid);
1075 $rootdir = "/proc/$pid/root";
1078 if (defined($delete)) {
1079 foreach my $opt (@$delete) {
1080 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1081 die "unable to delete required option '$opt'\n";
1082 } elsif ($opt eq 'swap') {
1083 delete $conf->{$opt};
1084 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1085 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1086 delete $conf->{$opt};
1087 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1088 $opt eq 'tty' || $opt eq 'console') {
1089 delete $conf->{$opt};
1090 push @nohotplug, $opt;
1092 } elsif ($opt =~ m/^net(\d)$/) {
1093 delete $conf->{$opt};
1096 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1100 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1104 # There's no separate swap size to configure, there's memory and "total"
1105 # memory (iow. memory+swap). This means we have to change them together.
1106 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1107 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1108 if (defined($wanted_memory) || defined($wanted_swap)) {
1110 $wanted_memory //= ($conf->{memory
} || 512);
1111 $wanted_swap //= ($conf->{swap
} || 0);
1113 my $total = $wanted_memory + $wanted_swap;
1115 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1116 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1118 $conf->{memory
} = $wanted_memory;
1119 $conf->{swap
} = $wanted_swap;
1121 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1124 foreach my $opt (keys %$param) {
1125 my $value = $param->{$opt};
1126 if ($opt eq 'hostname') {
1127 $conf->{$opt} = $value;
1128 } elsif ($opt eq 'onboot') {
1129 $conf->{$opt} = $value ?
1 : 0;
1130 } elsif ($opt eq 'startup') {
1131 $conf->{$opt} = $value;
1132 } elsif ($opt eq 'tty' || $opt eq 'console') {
1133 $conf->{$opt} = $value;
1134 push @nohotplug, $opt;
1136 } elsif ($opt eq 'nameserver') {
1137 my $list = verify_nameserver_list
($value);
1138 $conf->{$opt} = $list;
1139 push @nohotplug, $opt;
1141 } elsif ($opt eq 'searchdomain') {
1142 my $list = verify_searchdomain_list
($value);
1143 $conf->{$opt} = $list;
1144 push @nohotplug, $opt;
1146 } elsif ($opt eq 'cpulimit') {
1147 $conf->{$opt} = $value;
1148 push @nohotplug, $opt; # fixme: hotplug
1150 } elsif ($opt eq 'cpuunits') {
1151 $conf->{$opt} = $value;
1152 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1153 } elsif ($opt eq 'description') {
1154 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1155 } elsif ($opt =~ m/^net(\d+)$/) {
1157 my $net = parse_lxc_network
($value);
1159 $conf->{$opt} = print_lxc_network
($net);
1161 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1164 die "implement me: $opt";
1166 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1169 if ($running && scalar(@nohotplug)) {
1170 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1174 sub has_dev_console
{
1177 return !(defined($conf->{console
}) && !$conf->{console
});
1183 return $conf->{tty
} // $confdesc->{tty
}->{default};
1186 sub get_primary_ips
{
1189 # return data from net0
1191 return undef if !defined($conf->{net0
});
1192 my $net = parse_lxc_network
($conf->{net0
});
1194 my $ipv4 = $net->{ip
};
1196 if ($ipv4 =~ /^(dhcp|manual)$/) {
1202 my $ipv6 = $net->{ip6
};
1204 if ($ipv6 =~ /^(dhcp|manual)$/) {
1211 return ($ipv4, $ipv6);
1215 sub destroy_lxc_container
{
1216 my ($storage_cfg, $vmid, $conf) = @_;
1218 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1219 if (defined($rootinfo->{volume
})) {
1220 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1221 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1223 rmdir "/var/lib/lxc/$vmid/rootfs";
1224 unlink "/var/lib/lxc/$vmid/config";
1225 rmdir "/var/lib/lxc/$vmid";
1226 destroy_config
($vmid);
1228 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1229 #PVE::Tools::run_command($cmd);
1232 sub vm_stop_cleanup
{
1233 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1237 PVE
::LXC
::foreach_mountpoint
($conf, sub {
1238 my ($ms, $mountpoint) = @_;
1239 PVE
::Storage
::deactivate_volumes
($storage_cfg, [$mountpoint->{volume
}]);
1243 warn $@ if $@; # avoid errors - just warn
1246 my $safe_num_ne = sub {
1249 return 0 if !defined($a) && !defined($b);
1250 return 1 if !defined($a);
1251 return 1 if !defined($b);
1256 my $safe_string_ne = sub {
1259 return 0 if !defined($a) && !defined($b);
1260 return 1 if !defined($a);
1261 return 1 if !defined($b);
1267 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1269 if ($newnet->{type
} ne 'veth') {
1270 # for when there are physical interfaces
1271 die "cannot update interface of type $newnet->{type}";
1274 my $veth = "veth${vmid}i${netid}";
1275 my $eth = $newnet->{name
};
1277 if (my $oldnetcfg = $conf->{$opt}) {
1278 my $oldnet = parse_lxc_network
($oldnetcfg);
1280 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1281 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1283 PVE
::Network
::veth_delete
($veth);
1284 delete $conf->{$opt};
1285 PVE
::LXC
::write_config
($vmid, $conf);
1287 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1289 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1290 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1291 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1293 if ($oldnet->{bridge
}) {
1294 PVE
::Network
::tap_unplug
($veth);
1295 foreach (qw(bridge tag firewall)) {
1296 delete $oldnet->{$_};
1298 $conf->{$opt} = print_lxc_network
($oldnet);
1299 PVE
::LXC
::write_config
($vmid, $conf);
1302 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1303 foreach (qw(bridge tag firewall)) {
1304 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1306 $conf->{$opt} = print_lxc_network
($oldnet);
1307 PVE
::LXC
::write_config
($vmid, $conf);
1310 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1313 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1317 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1319 my $veth = "veth${vmid}i${netid}";
1320 my $vethpeer = $veth . "p";
1321 my $eth = $newnet->{name
};
1323 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1324 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1326 # attach peer in container
1327 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1328 PVE
::Tools
::run_command
($cmd);
1330 # link up peer in container
1331 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1332 PVE
::Tools
::run_command
($cmd);
1334 my $done = { type
=> 'veth' };
1335 foreach (qw(bridge tag firewall hwaddr name)) {
1336 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1338 $conf->{$opt} = print_lxc_network
($done);
1340 PVE
::LXC
::write_config
($vmid, $conf);
1343 sub update_ipconfig
{
1344 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1346 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1348 my $optdata = parse_lxc_network
($conf->{$opt});
1352 my $cmdargs = shift;
1353 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1355 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1357 my $change_ip_config = sub {
1358 my ($ipversion) = @_;
1360 my $family_opt = "-$ipversion";
1361 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1362 my $gw= "gw$suffix";
1363 my $ip= "ip$suffix";
1365 my $newip = $newnet->{$ip};
1366 my $newgw = $newnet->{$gw};
1367 my $oldip = $optdata->{$ip};
1369 my $change_ip = &$safe_string_ne($oldip, $newip);
1370 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1372 return if !$change_ip && !$change_gw;
1374 # step 1: add new IP, if this fails we cancel
1375 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1376 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1383 # step 2: replace gateway
1384 # If this fails we delete the added IP and cancel.
1385 # If it succeeds we save the config and delete the old IP, ignoring
1386 # errors. The config is then saved.
1387 # Note: 'ip route replace' can add
1390 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1393 # the route was not replaced, the old IP is still available
1394 # rollback (delete new IP) and cancel
1396 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1397 warn $@ if $@; # no need to die here
1402 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1403 # if the route was not deleted, the guest might have deleted it manually
1409 # from this point on we save the configuration
1410 # step 3: delete old IP ignoring errors
1411 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1412 # We need to enable promote_secondaries, otherwise our newly added
1413 # address will be removed along with the old one.
1416 if ($ipversion == 4) {
1417 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1418 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1419 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1421 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1423 warn $@ if $@; # no need to die here
1425 if ($ipversion == 4) {
1426 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1430 foreach my $property ($ip, $gw) {
1431 if ($newnet->{$property}) {
1432 $optdata->{$property} = $newnet->{$property};
1434 delete $optdata->{$property};
1437 $conf->{$opt} = print_lxc_network
($optdata);
1438 PVE
::LXC
::write_config
($vmid, $conf);
1439 $lxc_setup->setup_network($conf);
1442 &$change_ip_config(4);
1443 &$change_ip_config(6);
1447 # Internal snapshots
1449 # NOTE: Snapshot create/delete involves several non-atomic
1450 # action, and can take a long time.
1451 # So we try to avoid locking the file and use 'lock' variable
1452 # inside the config file instead.
1454 my $snapshot_copy_config = sub {
1455 my ($source, $dest) = @_;
1457 foreach my $k (keys %$source) {
1458 next if $k eq 'snapshots';
1459 next if $k eq 'snapstate';
1460 next if $k eq 'snaptime';
1461 next if $k eq 'vmstate';
1462 next if $k eq 'lock';
1463 next if $k eq 'digest';
1464 next if $k eq 'description';
1466 $dest->{$k} = $source->{$k};
1470 my $snapshot_prepare = sub {
1471 my ($vmid, $snapname, $comment) = @_;
1475 my $updatefn = sub {
1477 my $conf = load_config
($vmid);
1479 die "you can't take a snapshot if it's a template\n"
1480 if is_template
($conf);
1484 $conf->{lock} = 'snapshot';
1486 die "snapshot name '$snapname' already used\n"
1487 if defined($conf->{snapshots
}->{$snapname});
1489 my $storecfg = PVE
::Storage
::config
();
1490 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1492 $snap = $conf->{snapshots
}->{$snapname} = {};
1494 &$snapshot_copy_config($conf, $snap);
1496 $snap->{'snapstate'} = "prepare";
1497 $snap->{'snaptime'} = time();
1498 $snap->{'description'} = $comment if $comment;
1499 $conf->{snapshots
}->{$snapname} = $snap;
1501 PVE
::LXC
::write_config
($vmid, $conf);
1504 lock_container
($vmid, 10, $updatefn);
1509 my $snapshot_commit = sub {
1510 my ($vmid, $snapname) = @_;
1512 my $updatefn = sub {
1514 my $conf = load_config
($vmid);
1516 die "missing snapshot lock\n"
1517 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1519 die "snapshot '$snapname' does not exist\n"
1520 if !defined($conf->{snapshots
}->{$snapname});
1522 die "wrong snapshot state\n"
1523 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1524 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1526 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1527 delete $conf->{lock};
1528 $conf->{parent
} = $snapname;
1530 PVE
::LXC
::write_config
($vmid, $conf);
1533 lock_container
($vmid, 10 ,$updatefn);
1537 my ($feature, $conf, $storecfg, $snapname) = @_;
1539 #Fixme add other drives if necessary.
1542 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1543 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1545 return $err ?
0 : 1;
1548 sub snapshot_create
{
1549 my ($vmid, $snapname, $comment) = @_;
1551 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1553 my $conf = load_config
($vmid);
1555 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1556 my $running = check_running
($vmid);
1559 PVE
::Tools
::run_command
($cmd);
1562 my $storecfg = PVE
::Storage
::config
();
1563 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1564 my $volid = $rootinfo->{volume
};
1566 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1568 PVE
::Tools
::run_command
($cmd);
1571 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1572 &$snapshot_commit($vmid, $snapname);
1575 snapshot_delete
($vmid, $snapname, 1);
1580 sub snapshot_delete
{
1581 my ($vmid, $snapname, $force) = @_;
1587 my $updatefn = sub {
1589 $conf = load_config
($vmid);
1591 die "you can't delete a snapshot if vm is a template\n"
1592 if is_template
($conf);
1594 $snap = $conf->{snapshots
}->{$snapname};
1598 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1600 $snap->{snapstate
} = 'delete';
1602 PVE
::LXC
::write_config
($vmid, $conf);
1605 lock_container
($vmid, 10, $updatefn);
1607 my $storecfg = PVE
::Storage
::config
();
1609 my $del_snap = sub {
1613 if ($conf->{parent
} eq $snapname) {
1614 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1615 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1617 delete $conf->{parent
};
1621 delete $conf->{snapshots
}->{$snapname};
1623 PVE
::LXC
::write_config
($vmid, $conf);
1626 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1627 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1628 my $volid = $rootinfo->{volume
};
1631 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1635 if(!$err || ($err && $force)) {
1636 lock_container
($vmid, 10, $del_snap);
1638 die "Can't delete snapshot: $vmid $snapname $err\n";
1643 sub snapshot_rollback
{
1644 my ($vmid, $snapname) = @_;
1646 my $storecfg = PVE
::Storage
::config
();
1648 my $conf = load_config
($vmid);
1650 die "you can't rollback if vm is a template\n" if is_template
($conf);
1652 my $snap = $conf->{snapshots
}->{$snapname};
1654 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1656 my $rootfs = $snap->{rootfs
};
1657 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1658 my $volid = $rootinfo->{volume
};
1660 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1662 my $updatefn = sub {
1664 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1665 if $snap->{snapstate
};
1669 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1671 die "unable to rollback vm $vmid: vm is running\n"
1672 if check_running
($vmid);
1674 $conf->{lock} = 'rollback';
1678 # copy snapshot config to current config
1680 my $tmp_conf = $conf;
1681 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1682 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1683 delete $conf->{snaptime
};
1684 delete $conf->{snapname
};
1685 $conf->{parent
} = $snapname;
1687 PVE
::LXC
::write_config
($vmid, $conf);
1690 my $unlockfn = sub {
1691 delete $conf->{lock};
1692 PVE
::LXC
::write_config
($vmid, $conf);
1695 lock_container
($vmid, 10, $updatefn);
1697 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1699 lock_container
($vmid, 5, $unlockfn);
1702 sub template_create
{
1703 my ($vmid, $conf) = @_;
1705 my $storecfg = PVE
::Storage
::config
();
1707 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1708 my $volid = $rootinfo->{volume
};
1710 die "Template feature is not available for '$volid'\n"
1711 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1713 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1715 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1716 $rootinfo->{volume
} = $template_volid;
1717 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1719 write_config
($vmid, $conf);
1725 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1728 sub foreach_mountpoint
{
1729 my ($conf, $func) = @_;
1731 foreach my $ms (keys %$conf) {
1732 next if $ms !~ m/^mp(\d+)/ && $ms ne 'rootfs';
1734 my $mountpoint = parse_ct_mountpoint
($conf->{$ms});
1735 next if !$mountpoint;
1737 &$func($ms, $mountpoint);