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 sub write_pct_config
{
243 my ($filename, $conf) = @_;
245 delete $conf->{snapstate
}; # just to be sure
247 my $generate_raw_config = sub {
252 # add description as comment to top of file
253 my $descr = $conf->{description
} || '';
254 foreach my $cl (split(/\n/, $descr)) {
255 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
258 foreach my $key (sort keys %$conf) {
259 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
260 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
261 $raw .= "$key: $conf->{$key}\n";
264 if (my $lxcconf = $conf->{lxc
}) {
265 foreach my $entry (@$lxcconf) {
266 my ($k, $v) = @$entry;
274 my $raw = &$generate_raw_config($conf);
276 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
277 $raw .= "\n[$snapname]\n";
278 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
285 my ($key, $value) = @_;
287 die "unknown setting '$key'\n" if !$confdesc->{$key};
289 my $type = $confdesc->{$key}->{type
};
291 if (!defined($value)) {
292 die "got undefined value\n";
295 if ($value =~ m/[\n\r]/) {
296 die "property contains a line feed\n";
299 if ($type eq 'boolean') {
300 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
301 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
302 die "type check ('boolean') failed - got '$value'\n";
303 } elsif ($type eq 'integer') {
304 return int($1) if $value =~ m/^(\d+)$/;
305 die "type check ('integer') failed - got '$value'\n";
306 } elsif ($type eq 'number') {
307 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
308 die "type check ('number') failed - got '$value'\n";
309 } elsif ($type eq 'string') {
310 if (my $fmt = $confdesc->{$key}->{format
}) {
311 PVE
::JSONSchema
::check_format
($fmt, $value);
320 sub parse_pct_config
{
321 my ($filename, $raw) = @_;
323 return undef if !defined($raw);
326 digest
=> Digest
::SHA
::sha1_hex
($raw),
330 $filename =~ m
|/lxc/(\d
+).conf
$|
331 || die "got strange filename '$filename'";
339 my @lines = split(/\n/, $raw);
340 foreach my $line (@lines) {
341 next if $line =~ m/^\s*$/;
343 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
345 $conf->{description
} = $descr if $descr;
347 $conf = $res->{snapshots
}->{$section} = {};
351 if ($line =~ m/^\#(.*)\s*$/) {
352 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
356 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
359 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
360 push @{$conf->{lxc
}}, [$key, $value];
362 warn "vm $vmid - unable to parse config: $line\n";
364 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
365 $descr .= PVE
::Tools
::decode_text
($2);
366 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
367 $conf->{snapstate
} = $1;
368 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
371 eval { $value = check_type
($key, $value); };
372 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
373 $conf->{$key} = $value;
375 warn "vm $vmid - unable to parse config: $line\n";
379 $conf->{description
} = $descr if $descr;
381 delete $res->{snapstate
}; # just to be sure
387 my $vmlist = PVE
::Cluster
::get_vmlist
();
389 return $res if !$vmlist || !$vmlist->{ids
};
390 my $ids = $vmlist->{ids
};
392 foreach my $vmid (keys %$ids) {
393 next if !$vmid; # skip CT0
394 my $d = $ids->{$vmid};
395 next if !$d->{node
} || $d->{node
} ne $nodename;
396 next if !$d->{type
} || $d->{type
} ne 'lxc';
397 $res->{$vmid}->{type
} = 'lxc';
402 sub cfs_config_path
{
403 my ($vmid, $node) = @_;
405 $node = $nodename if !$node;
406 return "nodes/$node/lxc/$vmid.conf";
410 my ($vmid, $node) = @_;
412 my $cfspath = cfs_config_path
($vmid, $node);
413 return "/etc/pve/$cfspath";
419 my $cfspath = cfs_config_path
($vmid);
421 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
422 die "container $vmid does not exists\n" if !defined($conf);
428 my ($vmid, $conf) = @_;
430 my $dir = "/etc/pve/nodes/$nodename/lxc";
433 write_config
($vmid, $conf);
439 unlink config_file
($vmid, $nodename);
443 my ($vmid, $conf) = @_;
445 my $cfspath = cfs_config_path
($vmid);
447 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
450 # flock: we use one file handle per process, so lock file
451 # can be called multiple times and succeeds for the same process.
453 my $lock_handles = {};
454 my $lockdir = "/run/lock/lxc";
459 return "$lockdir/pve-config-{$vmid}.lock";
463 my ($vmid, $timeout) = @_;
465 $timeout = 10 if !$timeout;
468 my $filename = lock_filename
($vmid);
470 mkdir $lockdir if !-d
$lockdir;
472 my $lock_func = sub {
473 if (!$lock_handles->{$$}->{$filename}) {
474 my $fh = new IO
::File
(">>$filename") ||
475 die "can't open file - $!\n";
476 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
479 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
480 print STDERR
"trying to aquire lock...";
483 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
484 # try again on EINTR (see bug #273)
485 if ($success || ($! != EINTR
)) {
490 print STDERR
" failed\n";
491 die "can't aquire lock - $!\n";
494 $lock_handles->{$$}->{$filename}->{refcount
}++;
496 print STDERR
" OK\n";
500 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
503 die "can't lock file '$filename' - $err";
510 my $filename = lock_filename
($vmid);
512 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
513 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
514 if ($refcount <= 0) {
515 $lock_handles->{$$}->{$filename} = undef;
522 my ($vmid, $timeout, $code, @param) = @_;
526 lock_aquire
($vmid, $timeout);
527 eval { $res = &$code(@param) };
539 return defined($confdesc->{$name});
542 # add JSON properties for create and set function
543 sub json_config_properties
{
546 foreach my $opt (keys %$confdesc) {
547 next if $opt eq 'parent' || $opt eq 'snaptime';
548 next if $prop->{$opt};
549 $prop->{$opt} = $confdesc->{$opt};
555 sub json_config_properties_no_rootfs
{
558 foreach my $opt (keys %$confdesc) {
559 next if $prop->{$opt};
560 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
561 $prop->{$opt} = $confdesc->{$opt};
567 # container status helpers
569 sub list_active_containers
{
571 my $filename = "/proc/net/unix";
573 # similar test is used by lcxcontainers.c: list_active_containers
576 my $fh = IO
::File-
>new ($filename, "r");
579 while (defined(my $line = <$fh>)) {
580 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
582 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
593 # warning: this is slow
597 my $active_hash = list_active_containers
();
599 return 1 if defined($active_hash->{$vmid});
604 sub get_container_disk_usage
{
607 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
617 if (my ($fsid, $total, $used, $avail) = $line =~
618 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
626 eval { PVE
::Tools
::run_command
($cmd, timeout
=> 1, outfunc
=> $parser); };
635 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
637 my $active_hash = list_active_containers
();
639 foreach my $vmid (keys %$list) {
640 my $d = $list->{$vmid};
642 my $running = defined($active_hash->{$vmid});
644 $d->{status
} = $running ?
'running' : 'stopped';
646 my $cfspath = cfs_config_path
($vmid);
647 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
649 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
650 $d->{name
} =~ s/[\s]//g;
652 $d->{cpus
} = $conf->{cpulimit
} // 0;
655 my $res = get_container_disk_usage
($vmid);
656 $d->{disk
} = $res->{used
};
657 $d->{maxdisk
} = $res->{total
};
660 # use 4GB by default ??
661 if (my $rootfs = $conf->{rootfs
}) {
662 my $rootinfo = parse_ct_mountpoint
($rootfs);
663 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
665 $d->{maxdisk
} = 4*1024*1024*1024;
671 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
672 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
683 $d->{template
} = is_template
($conf);
686 foreach my $vmid (keys %$list) {
687 my $d = $list->{$vmid};
688 next if $d->{status
} ne 'running';
690 $d->{uptime
} = 100; # fixme:
692 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
693 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
695 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
696 my @bytes = split(/\n/, $blkio_bytes);
697 foreach my $byte (@bytes) {
698 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
699 $d->{diskread
} = $2 if $key eq 'Read';
700 $d->{diskwrite
} = $2 if $key eq 'Write';
708 my $parse_size = sub {
711 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
712 my ($size, $unit) = ($1, $3);
715 $size = $size * 1024;
716 } elsif ($unit eq 'M') {
717 $size = $size * 1024 * 1024;
718 } elsif ($unit eq 'G') {
719 $size = $size * 1024 * 1024 * 1024;
725 sub parse_ct_mountpoint
{
732 foreach my $p (split (/,/, $data)) {
733 next if $p =~ m/^\s*$/;
735 if ($p =~ m/^(volume|backup|size)=(.+)$/) {
736 my ($k, $v) = ($1, $2);
737 return undef if defined($res->{$k});
740 if (!$res->{volume
} && $p !~ m/=/) {
748 return undef if !$res->{volume
};
750 return undef if $res->{backup
} && $res->{backup
} !~ m/^(yes|no)$/;
753 return undef if !defined($res->{size
} = &$parse_size($res->{size
}));
759 sub print_ct_mountpoint
{
764 die "missing volume\n" if !$info->{volume
};
766 foreach my $o ('size', 'backup') {
767 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
770 return "$info->{volume}$opts";
773 sub print_lxc_network
{
776 die "no network name defined\n" if !$net->{name
};
778 my $res = "name=$net->{name}";
780 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
781 next if !defined($net->{$k});
782 $res .= ",$k=$net->{$k}";
788 sub parse_lxc_network
{
793 return $res if !$data;
795 foreach my $pv (split (/,/, $data)) {
796 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
803 $res->{type
} = 'veth';
804 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
809 sub read_cgroup_value
{
810 my ($group, $vmid, $name, $full) = @_;
812 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
814 return PVE
::Tools
::file_get_contents
($path) if $full;
816 return PVE
::Tools
::file_read_firstline
($path);
819 sub write_cgroup_value
{
820 my ($group, $vmid, $name, $value) = @_;
822 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
823 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
827 sub find_lxc_console_pids
{
831 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
834 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
837 my @args = split(/\0/, $cmdline);
839 # serach for lxc-console -n <vmid>
840 return if scalar(@args) != 3;
841 return if $args[1] ne '-n';
842 return if $args[2] !~ m/^\d+$/;
843 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
847 push @{$res->{$vmid}}, $pid;
859 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
861 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid], outfunc
=> $parser);
863 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
868 my $ipv4_reverse_mask = [
904 # Note: we cannot use Net:IP, because that only allows strict
906 sub parse_ipv4_cidr
{
907 my ($cidr, $noerr) = @_;
909 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
910 return { address
=> $1, netmask
=> $ipv4_reverse_mask->[$2] };
913 return undef if $noerr;
915 die "unable to parse ipv4 address/mask\n";
921 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
924 sub update_lxc_config
{
925 my ($storage_cfg, $vmid, $conf) = @_;
927 my $dir = "/var/lib/lxc/$vmid";
929 if ($conf->{template
}) {
931 unlink "$dir/config";
938 die "missing 'arch' - internal error" if !$conf->{arch
};
939 $raw .= "lxc.arch = $conf->{arch}\n";
941 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
942 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
943 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
948 $raw .= "lxc.console = none\n" if !$conf->{console
};
950 my $ttycount = get_tty_count
($conf);
951 $raw .= "lxc.tty = $ttycount\n";
953 my $utsname = $conf->{hostname
} || "CT$vmid";
954 $raw .= "lxc.utsname = $utsname\n";
956 my $memory = $conf->{memory
} || 512;
957 my $swap = $conf->{swap
} // 0;
959 my $lxcmem = int($memory*1024*1024);
960 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
962 my $lxcswap = int(($memory + $swap)*1024*1024);
963 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
965 if (my $cpulimit = $conf->{cpulimit
}) {
966 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
967 my $value = int(100000*$cpulimit);
968 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
971 my $shares = $conf->{cpuunits
} || 1024;
972 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
974 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
975 my $volid = $rootinfo->{volume
};
976 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
978 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
979 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
981 die "unable to use template as rootfs\n" if $isBase;
983 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
984 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
986 if ($format eq 'subvol') {
987 $raw .= "lxc.rootfs = $path\n";
988 } elsif ($format eq 'raw') {
990 $raw .= "lxc.rootfs = loop:$path\n";
991 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
992 $raw .= "lxc.rootfs = $path\n";
994 die "unsupported storage type '$scfg->{type}'\n";
997 die "unsupported image format '$format'\n";
1001 foreach my $k (keys %$conf) {
1002 next if $k !~ m/^net(\d+)$/;
1004 my $d = parse_lxc_network
($conf->{$k});
1006 $raw .= "lxc.network.type = veth\n";
1007 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1008 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1009 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1010 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1013 if (my $lxcconf = $conf->{lxc
}) {
1014 foreach my $entry (@$lxcconf) {
1015 my ($k, $v) = @$entry;
1016 $netcount++ if $k eq 'lxc.network.type';
1017 $raw .= "$k = $v\n";
1021 $raw .= "lxc.network.type = empty\n" if !$netcount;
1023 File
::Path
::mkpath
("$dir/rootfs");
1025 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1028 # verify and cleanup nameserver list (replace \0 with ' ')
1029 sub verify_nameserver_list
{
1030 my ($nameserver_list) = @_;
1033 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1034 PVE
::JSONSchema
::pve_verify_ip
($server);
1035 push @list, $server;
1038 return join(' ', @list);
1041 sub verify_searchdomain_list
{
1042 my ($searchdomain_list) = @_;
1045 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1046 # todo: should we add checks for valid dns domains?
1047 push @list, $server;
1050 return join(' ', @list);
1053 sub update_pct_config
{
1054 my ($vmid, $conf, $running, $param, $delete) = @_;
1060 my $pid = find_lxc_pid
($vmid);
1061 $rootdir = "/proc/$pid/root";
1064 if (defined($delete)) {
1065 foreach my $opt (@$delete) {
1066 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1067 die "unable to delete required option '$opt'\n";
1068 } elsif ($opt eq 'swap') {
1069 delete $conf->{$opt};
1070 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1071 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1072 delete $conf->{$opt};
1073 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1074 $opt eq 'tty' || $opt eq 'console') {
1075 delete $conf->{$opt};
1076 push @nohotplug, $opt;
1078 } elsif ($opt =~ m/^net(\d)$/) {
1079 delete $conf->{$opt};
1082 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1086 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1090 # There's no separate swap size to configure, there's memory and "total"
1091 # memory (iow. memory+swap). This means we have to change them together.
1092 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1093 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1094 if (defined($wanted_memory) || defined($wanted_swap)) {
1096 $wanted_memory //= ($conf->{memory
} || 512);
1097 $wanted_swap //= ($conf->{swap
} || 0);
1099 my $total = $wanted_memory + $wanted_swap;
1101 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1102 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1104 $conf->{memory
} = $wanted_memory;
1105 $conf->{swap
} = $wanted_swap;
1107 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1110 foreach my $opt (keys %$param) {
1111 my $value = $param->{$opt};
1112 if ($opt eq 'hostname') {
1113 $conf->{$opt} = $value;
1114 } elsif ($opt eq 'onboot') {
1115 $conf->{$opt} = $value ?
1 : 0;
1116 } elsif ($opt eq 'startup') {
1117 $conf->{$opt} = $value;
1118 } elsif ($opt eq 'tty' || $opt eq 'console') {
1119 $conf->{$opt} = $value;
1120 push @nohotplug, $opt;
1122 } elsif ($opt eq 'nameserver') {
1123 my $list = verify_nameserver_list
($value);
1124 $conf->{$opt} = $list;
1125 push @nohotplug, $opt;
1127 } elsif ($opt eq 'searchdomain') {
1128 my $list = verify_searchdomain_list
($value);
1129 $conf->{$opt} = $list;
1130 push @nohotplug, $opt;
1132 } elsif ($opt eq 'cpulimit') {
1133 $conf->{$opt} = $value;
1134 push @nohotplug, $opt; # fixme: hotplug
1136 } elsif ($opt eq 'cpuunits') {
1137 $conf->{$opt} = $value;
1138 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1139 } elsif ($opt eq 'description') {
1140 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1141 } elsif ($opt =~ m/^net(\d+)$/) {
1143 my $net = parse_lxc_network
($value);
1145 $conf->{$opt} = print_lxc_network
($net);
1147 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1150 die "implement me: $opt";
1152 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1155 if ($running && scalar(@nohotplug)) {
1156 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1163 return $conf->{tty
} // $confdesc->{tty
}->{default};
1166 sub get_primary_ips
{
1169 # return data from net0
1171 return undef if !defined($conf->{net0
});
1172 my $net = parse_lxc_network
($conf->{net0
});
1174 my $ipv4 = $net->{ip
};
1176 if ($ipv4 =~ /^(dhcp|manual)$/) {
1182 my $ipv6 = $net->{ip6
};
1184 if ($ipv6 =~ /^(dhcp|manual)$/) {
1191 return ($ipv4, $ipv6);
1195 sub destroy_lxc_container
{
1196 my ($storage_cfg, $vmid, $conf) = @_;
1198 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1199 if (defined($rootinfo->{volume
})) {
1200 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1201 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1203 rmdir "/var/lib/lxc/$vmid/rootfs";
1204 unlink "/var/lib/lxc/$vmid/config";
1205 rmdir "/var/lib/lxc/$vmid";
1206 destroy_config
($vmid);
1208 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1209 #PVE::Tools::run_command($cmd);
1212 sub vm_stop_cleanup
{
1213 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1217 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1218 PVE
::Storage
::deactivate_volumes
($storeage_cfg, [$rootinfo->{volume
}]);
1221 warn $@ if $@; # avoid errors - just warn
1224 my $safe_num_ne = sub {
1227 return 0 if !defined($a) && !defined($b);
1228 return 1 if !defined($a);
1229 return 1 if !defined($b);
1234 my $safe_string_ne = sub {
1237 return 0 if !defined($a) && !defined($b);
1238 return 1 if !defined($a);
1239 return 1 if !defined($b);
1245 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1247 if ($newnet->{type
} ne 'veth') {
1248 # for when there are physical interfaces
1249 die "cannot update interface of type $newnet->{type}";
1252 my $veth = "veth${vmid}i${netid}";
1253 my $eth = $newnet->{name
};
1255 if (my $oldnetcfg = $conf->{$opt}) {
1256 my $oldnet = parse_lxc_network
($oldnetcfg);
1258 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1259 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1261 PVE
::Network
::veth_delete
($veth);
1262 delete $conf->{$opt};
1263 PVE
::LXC
::write_config
($vmid, $conf);
1265 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1267 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1268 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1269 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1271 if ($oldnet->{bridge
}) {
1272 PVE
::Network
::tap_unplug
($veth);
1273 foreach (qw(bridge tag firewall)) {
1274 delete $oldnet->{$_};
1276 $conf->{$opt} = print_lxc_network
($oldnet);
1277 PVE
::LXC
::write_config
($vmid, $conf);
1280 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1281 foreach (qw(bridge tag firewall)) {
1282 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1284 $conf->{$opt} = print_lxc_network
($oldnet);
1285 PVE
::LXC
::write_config
($vmid, $conf);
1288 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1291 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1295 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1297 my $veth = "veth${vmid}i${netid}";
1298 my $vethpeer = $veth . "p";
1299 my $eth = $newnet->{name
};
1301 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1302 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1304 # attach peer in container
1305 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1306 PVE
::Tools
::run_command
($cmd);
1308 # link up peer in container
1309 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1310 PVE
::Tools
::run_command
($cmd);
1312 my $done = { type
=> 'veth' };
1313 foreach (qw(bridge tag firewall hwaddr name)) {
1314 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1316 $conf->{$opt} = print_lxc_network
($done);
1318 PVE
::LXC
::write_config
($vmid, $conf);
1321 sub update_ipconfig
{
1322 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1324 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1326 my $optdata = parse_lxc_network
($conf->{$opt});
1330 my $cmdargs = shift;
1331 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1333 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1335 my $change_ip_config = sub {
1336 my ($ipversion) = @_;
1338 my $family_opt = "-$ipversion";
1339 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1340 my $gw= "gw$suffix";
1341 my $ip= "ip$suffix";
1343 my $newip = $newnet->{$ip};
1344 my $newgw = $newnet->{$gw};
1345 my $oldip = $optdata->{$ip};
1347 my $change_ip = &$safe_string_ne($oldip, $newip);
1348 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1350 return if !$change_ip && !$change_gw;
1352 # step 1: add new IP, if this fails we cancel
1353 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1354 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1361 # step 2: replace gateway
1362 # If this fails we delete the added IP and cancel.
1363 # If it succeeds we save the config and delete the old IP, ignoring
1364 # errors. The config is then saved.
1365 # Note: 'ip route replace' can add
1368 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1371 # the route was not replaced, the old IP is still available
1372 # rollback (delete new IP) and cancel
1374 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1375 warn $@ if $@; # no need to die here
1380 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1381 # if the route was not deleted, the guest might have deleted it manually
1387 # from this point on we save the configuration
1388 # step 3: delete old IP ignoring errors
1389 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1390 # We need to enable promote_secondaries, otherwise our newly added
1391 # address will be removed along with the old one.
1394 if ($ipversion == 4) {
1395 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1396 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1397 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1399 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1401 warn $@ if $@; # no need to die here
1403 if ($ipversion == 4) {
1404 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1408 foreach my $property ($ip, $gw) {
1409 if ($newnet->{$property}) {
1410 $optdata->{$property} = $newnet->{$property};
1412 delete $optdata->{$property};
1415 $conf->{$opt} = print_lxc_network
($optdata);
1416 PVE
::LXC
::write_config
($vmid, $conf);
1417 $lxc_setup->setup_network($conf);
1420 &$change_ip_config(4);
1421 &$change_ip_config(6);
1425 # Internal snapshots
1427 # NOTE: Snapshot create/delete involves several non-atomic
1428 # action, and can take a long time.
1429 # So we try to avoid locking the file and use 'lock' variable
1430 # inside the config file instead.
1432 my $snapshot_copy_config = sub {
1433 my ($source, $dest) = @_;
1435 foreach my $k (keys %$source) {
1436 next if $k eq 'snapshots';
1437 next if $k eq 'snapstate';
1438 next if $k eq 'snaptime';
1439 next if $k eq 'vmstate';
1440 next if $k eq 'lock';
1441 next if $k eq 'digest';
1442 next if $k eq 'description';
1444 $dest->{$k} = $source->{$k};
1448 my $snapshot_prepare = sub {
1449 my ($vmid, $snapname, $comment) = @_;
1453 my $updatefn = sub {
1455 my $conf = load_config
($vmid);
1457 die "you can't take a snapshot if it's a template\n"
1458 if is_template
($conf);
1462 $conf->{lock} = 'snapshot';
1464 die "snapshot name '$snapname' already used\n"
1465 if defined($conf->{snapshots
}->{$snapname});
1467 my $storecfg = PVE
::Storage
::config
();
1468 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1470 $snap = $conf->{snapshots
}->{$snapname} = {};
1472 &$snapshot_copy_config($conf, $snap);
1474 $snap->{'snapstate'} = "prepare";
1475 $snap->{'snaptime'} = time();
1476 $snap->{'description'} = $comment if $comment;
1477 $conf->{snapshots
}->{$snapname} = $snap;
1479 PVE
::LXC
::write_config
($vmid, $conf);
1482 lock_container
($vmid, 10, $updatefn);
1487 my $snapshot_commit = sub {
1488 my ($vmid, $snapname) = @_;
1490 my $updatefn = sub {
1492 my $conf = load_config
($vmid);
1494 die "missing snapshot lock\n"
1495 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1497 die "snapshot '$snapname' does not exist\n"
1498 if !defined($conf->{snapshots
}->{$snapname});
1500 die "wrong snapshot state\n"
1501 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1502 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1504 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1505 delete $conf->{lock};
1506 $conf->{parent
} = $snapname;
1508 PVE
::LXC
::write_config
($vmid, $conf);
1511 lock_container
($vmid, 10 ,$updatefn);
1515 my ($feature, $conf, $storecfg, $snapname) = @_;
1517 #Fixme add other drives if necessary.
1520 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1521 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1523 return $err ?
0 : 1;
1526 sub snapshot_create
{
1527 my ($vmid, $snapname, $comment) = @_;
1529 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1531 my $conf = load_config
($vmid);
1533 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1534 my $running = check_running
($vmid);
1537 PVE
::Tools
::run_command
($cmd);
1540 my $storecfg = PVE
::Storage
::config
();
1541 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1542 my $volid = $rootinfo->{volume
};
1544 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1546 PVE
::Tools
::run_command
($cmd);
1549 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1550 &$snapshot_commit($vmid, $snapname);
1553 snapshot_delete
($vmid, $snapname, 1);
1558 sub snapshot_delete
{
1559 my ($vmid, $snapname, $force) = @_;
1565 my $updatefn = sub {
1567 $conf = load_config
($vmid);
1569 die "you can't delete a snapshot if vm is a template\n"
1570 if is_template
($conf);
1572 $snap = $conf->{snapshots
}->{$snapname};
1576 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1578 $snap->{snapstate
} = 'delete';
1580 PVE
::LXC
::write_config
($vmid, $conf);
1583 lock_container
($vmid, 10, $updatefn);
1585 my $storecfg = PVE
::Storage
::config
();
1587 my $del_snap = sub {
1591 if ($conf->{parent
} eq $snapname) {
1592 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1593 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1595 delete $conf->{parent
};
1599 delete $conf->{snapshots
}->{$snapname};
1601 PVE
::LXC
::write_config
($vmid, $conf);
1604 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1605 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1606 my $volid = $rootinfo->{volume
};
1609 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1613 if(!$err || ($err && $force)) {
1614 lock_container
($vmid, 10, $del_snap);
1616 die "Can't delete snapshot: $vmid $snapname $err\n";
1621 sub snapshot_rollback
{
1622 my ($vmid, $snapname) = @_;
1624 my $storecfg = PVE
::Storage
::config
();
1626 my $conf = load_config
($vmid);
1628 die "you can't rollback if vm is a template\n" if is_template
($conf);
1630 my $snap = $conf->{snapshots
}->{$snapname};
1632 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1634 my $rootfs = $snap->{rootfs
};
1635 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1636 my $volid = $rootinfo->{volume
};
1638 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1640 my $updatefn = sub {
1642 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1643 if $snap->{snapstate
};
1647 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1649 die "unable to rollback vm $vmid: vm is running\n"
1650 if check_running
($vmid);
1652 $conf->{lock} = 'rollback';
1656 # copy snapshot config to current config
1658 my $tmp_conf = $conf;
1659 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1660 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1661 delete $conf->{snaptime
};
1662 delete $conf->{snapname
};
1663 $conf->{parent
} = $snapname;
1665 PVE
::LXC
::write_config
($vmid, $conf);
1668 my $unlockfn = sub {
1669 delete $conf->{lock};
1670 PVE
::LXC
::write_config
($vmid, $conf);
1673 lock_container
($vmid, 10, $updatefn);
1675 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1677 lock_container
($vmid, 5, $unlockfn);
1680 sub template_create
{
1681 my ($vmid, $conf) = @_;
1683 my $storecfg = PVE
::Storage
::config
();
1685 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1686 my $volid = $rootinfo->{volume
};
1688 die "Template feature is not available for '$volid'\n"
1689 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1691 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1693 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1694 $rootinfo->{volume
} = $template_volid;
1695 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1697 write_config
($vmid, $conf);
1703 return 1 if defined $conf->{template
} && $conf->{template
} == 1;