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 if (defined($conf->{console
}) && !$conf->{console
}) {
949 $raw .= "lxc.console = none\n";
950 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
953 my $ttycount = get_tty_count
($conf);
954 $raw .= "lxc.tty = $ttycount\n";
956 my $utsname = $conf->{hostname
} || "CT$vmid";
957 $raw .= "lxc.utsname = $utsname\n";
959 my $memory = $conf->{memory
} || 512;
960 my $swap = $conf->{swap
} // 0;
962 my $lxcmem = int($memory*1024*1024);
963 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
965 my $lxcswap = int(($memory + $swap)*1024*1024);
966 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
968 if (my $cpulimit = $conf->{cpulimit
}) {
969 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
970 my $value = int(100000*$cpulimit);
971 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
974 my $shares = $conf->{cpuunits
} || 1024;
975 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
977 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
978 my $volid = $rootinfo->{volume
};
979 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid);
981 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
982 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
984 die "unable to use template as rootfs\n" if $isBase;
986 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
987 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
989 if ($format eq 'subvol') {
990 $raw .= "lxc.rootfs = $path\n";
991 } elsif ($format eq 'raw') {
993 $raw .= "lxc.rootfs = loop:$path\n";
994 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'rbd') {
995 $raw .= "lxc.rootfs = $path\n";
997 die "unsupported storage type '$scfg->{type}'\n";
1000 die "unsupported image format '$format'\n";
1004 foreach my $k (keys %$conf) {
1005 next if $k !~ m/^net(\d+)$/;
1007 my $d = parse_lxc_network
($conf->{$k});
1009 $raw .= "lxc.network.type = veth\n";
1010 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1011 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1012 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1013 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1016 if (my $lxcconf = $conf->{lxc
}) {
1017 foreach my $entry (@$lxcconf) {
1018 my ($k, $v) = @$entry;
1019 $netcount++ if $k eq 'lxc.network.type';
1020 $raw .= "$k = $v\n";
1024 $raw .= "lxc.network.type = empty\n" if !$netcount;
1026 File
::Path
::mkpath
("$dir/rootfs");
1028 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1031 # verify and cleanup nameserver list (replace \0 with ' ')
1032 sub verify_nameserver_list
{
1033 my ($nameserver_list) = @_;
1036 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1037 PVE
::JSONSchema
::pve_verify_ip
($server);
1038 push @list, $server;
1041 return join(' ', @list);
1044 sub verify_searchdomain_list
{
1045 my ($searchdomain_list) = @_;
1048 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1049 # todo: should we add checks for valid dns domains?
1050 push @list, $server;
1053 return join(' ', @list);
1056 sub update_pct_config
{
1057 my ($vmid, $conf, $running, $param, $delete) = @_;
1063 my $pid = find_lxc_pid
($vmid);
1064 $rootdir = "/proc/$pid/root";
1067 if (defined($delete)) {
1068 foreach my $opt (@$delete) {
1069 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1070 die "unable to delete required option '$opt'\n";
1071 } elsif ($opt eq 'swap') {
1072 delete $conf->{$opt};
1073 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1074 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1075 delete $conf->{$opt};
1076 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1077 $opt eq 'tty' || $opt eq 'console') {
1078 delete $conf->{$opt};
1079 push @nohotplug, $opt;
1081 } elsif ($opt =~ m/^net(\d)$/) {
1082 delete $conf->{$opt};
1085 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1089 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1093 # There's no separate swap size to configure, there's memory and "total"
1094 # memory (iow. memory+swap). This means we have to change them together.
1095 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1096 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1097 if (defined($wanted_memory) || defined($wanted_swap)) {
1099 $wanted_memory //= ($conf->{memory
} || 512);
1100 $wanted_swap //= ($conf->{swap
} || 0);
1102 my $total = $wanted_memory + $wanted_swap;
1104 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1105 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1107 $conf->{memory
} = $wanted_memory;
1108 $conf->{swap
} = $wanted_swap;
1110 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1113 foreach my $opt (keys %$param) {
1114 my $value = $param->{$opt};
1115 if ($opt eq 'hostname') {
1116 $conf->{$opt} = $value;
1117 } elsif ($opt eq 'onboot') {
1118 $conf->{$opt} = $value ?
1 : 0;
1119 } elsif ($opt eq 'startup') {
1120 $conf->{$opt} = $value;
1121 } elsif ($opt eq 'tty' || $opt eq 'console') {
1122 $conf->{$opt} = $value;
1123 push @nohotplug, $opt;
1125 } elsif ($opt eq 'nameserver') {
1126 my $list = verify_nameserver_list
($value);
1127 $conf->{$opt} = $list;
1128 push @nohotplug, $opt;
1130 } elsif ($opt eq 'searchdomain') {
1131 my $list = verify_searchdomain_list
($value);
1132 $conf->{$opt} = $list;
1133 push @nohotplug, $opt;
1135 } elsif ($opt eq 'cpulimit') {
1136 $conf->{$opt} = $value;
1137 push @nohotplug, $opt; # fixme: hotplug
1139 } elsif ($opt eq 'cpuunits') {
1140 $conf->{$opt} = $value;
1141 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1142 } elsif ($opt eq 'description') {
1143 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1144 } elsif ($opt =~ m/^net(\d+)$/) {
1146 my $net = parse_lxc_network
($value);
1148 $conf->{$opt} = print_lxc_network
($net);
1150 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1153 die "implement me: $opt";
1155 PVE
::LXC
::write_config
($vmid, $conf) if $running;
1158 if ($running && scalar(@nohotplug)) {
1159 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1166 return $conf->{tty
} // $confdesc->{tty
}->{default};
1169 sub get_primary_ips
{
1172 # return data from net0
1174 return undef if !defined($conf->{net0
});
1175 my $net = parse_lxc_network
($conf->{net0
});
1177 my $ipv4 = $net->{ip
};
1179 if ($ipv4 =~ /^(dhcp|manual)$/) {
1185 my $ipv6 = $net->{ip6
};
1187 if ($ipv6 =~ /^(dhcp|manual)$/) {
1194 return ($ipv4, $ipv6);
1198 sub destroy_lxc_container
{
1199 my ($storage_cfg, $vmid, $conf) = @_;
1201 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1202 if (defined($rootinfo->{volume
})) {
1203 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $rootinfo->{volume
});
1204 PVE
::Storage
::vdisk_free
($storage_cfg, $rootinfo->{volume
}) if $vmid == $owner;;
1206 rmdir "/var/lib/lxc/$vmid/rootfs";
1207 unlink "/var/lib/lxc/$vmid/config";
1208 rmdir "/var/lib/lxc/$vmid";
1209 destroy_config
($vmid);
1211 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1212 #PVE::Tools::run_command($cmd);
1215 sub vm_stop_cleanup
{
1216 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1220 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1221 PVE
::Storage
::deactivate_volumes
($storeage_cfg, [$rootinfo->{volume
}]);
1224 warn $@ if $@; # avoid errors - just warn
1227 my $safe_num_ne = sub {
1230 return 0 if !defined($a) && !defined($b);
1231 return 1 if !defined($a);
1232 return 1 if !defined($b);
1237 my $safe_string_ne = sub {
1240 return 0 if !defined($a) && !defined($b);
1241 return 1 if !defined($a);
1242 return 1 if !defined($b);
1248 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1250 if ($newnet->{type
} ne 'veth') {
1251 # for when there are physical interfaces
1252 die "cannot update interface of type $newnet->{type}";
1255 my $veth = "veth${vmid}i${netid}";
1256 my $eth = $newnet->{name
};
1258 if (my $oldnetcfg = $conf->{$opt}) {
1259 my $oldnet = parse_lxc_network
($oldnetcfg);
1261 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1262 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1264 PVE
::Network
::veth_delete
($veth);
1265 delete $conf->{$opt};
1266 PVE
::LXC
::write_config
($vmid, $conf);
1268 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1270 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1271 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1272 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1274 if ($oldnet->{bridge
}) {
1275 PVE
::Network
::tap_unplug
($veth);
1276 foreach (qw(bridge tag firewall)) {
1277 delete $oldnet->{$_};
1279 $conf->{$opt} = print_lxc_network
($oldnet);
1280 PVE
::LXC
::write_config
($vmid, $conf);
1283 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1284 foreach (qw(bridge tag firewall)) {
1285 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1287 $conf->{$opt} = print_lxc_network
($oldnet);
1288 PVE
::LXC
::write_config
($vmid, $conf);
1291 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1294 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1298 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1300 my $veth = "veth${vmid}i${netid}";
1301 my $vethpeer = $veth . "p";
1302 my $eth = $newnet->{name
};
1304 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1305 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1307 # attach peer in container
1308 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1309 PVE
::Tools
::run_command
($cmd);
1311 # link up peer in container
1312 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1313 PVE
::Tools
::run_command
($cmd);
1315 my $done = { type
=> 'veth' };
1316 foreach (qw(bridge tag firewall hwaddr name)) {
1317 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1319 $conf->{$opt} = print_lxc_network
($done);
1321 PVE
::LXC
::write_config
($vmid, $conf);
1324 sub update_ipconfig
{
1325 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1327 my $lxc_setup = PVE
::LXCSetup-
>new($conf, $rootdir);
1329 my $optdata = parse_lxc_network
($conf->{$opt});
1333 my $cmdargs = shift;
1334 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1336 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1338 my $change_ip_config = sub {
1339 my ($ipversion) = @_;
1341 my $family_opt = "-$ipversion";
1342 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1343 my $gw= "gw$suffix";
1344 my $ip= "ip$suffix";
1346 my $newip = $newnet->{$ip};
1347 my $newgw = $newnet->{$gw};
1348 my $oldip = $optdata->{$ip};
1350 my $change_ip = &$safe_string_ne($oldip, $newip);
1351 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1353 return if !$change_ip && !$change_gw;
1355 # step 1: add new IP, if this fails we cancel
1356 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
1357 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1364 # step 2: replace gateway
1365 # If this fails we delete the added IP and cancel.
1366 # If it succeeds we save the config and delete the old IP, ignoring
1367 # errors. The config is then saved.
1368 # Note: 'ip route replace' can add
1371 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
1374 # the route was not replaced, the old IP is still available
1375 # rollback (delete new IP) and cancel
1377 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1378 warn $@ if $@; # no need to die here
1383 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1384 # if the route was not deleted, the guest might have deleted it manually
1390 # from this point on we save the configuration
1391 # step 3: delete old IP ignoring errors
1392 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1393 # We need to enable promote_secondaries, otherwise our newly added
1394 # address will be removed along with the old one.
1397 if ($ipversion == 4) {
1398 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1399 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1400 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1402 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1404 warn $@ if $@; # no need to die here
1406 if ($ipversion == 4) {
1407 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1411 foreach my $property ($ip, $gw) {
1412 if ($newnet->{$property}) {
1413 $optdata->{$property} = $newnet->{$property};
1415 delete $optdata->{$property};
1418 $conf->{$opt} = print_lxc_network
($optdata);
1419 PVE
::LXC
::write_config
($vmid, $conf);
1420 $lxc_setup->setup_network($conf);
1423 &$change_ip_config(4);
1424 &$change_ip_config(6);
1428 # Internal snapshots
1430 # NOTE: Snapshot create/delete involves several non-atomic
1431 # action, and can take a long time.
1432 # So we try to avoid locking the file and use 'lock' variable
1433 # inside the config file instead.
1435 my $snapshot_copy_config = sub {
1436 my ($source, $dest) = @_;
1438 foreach my $k (keys %$source) {
1439 next if $k eq 'snapshots';
1440 next if $k eq 'snapstate';
1441 next if $k eq 'snaptime';
1442 next if $k eq 'vmstate';
1443 next if $k eq 'lock';
1444 next if $k eq 'digest';
1445 next if $k eq 'description';
1447 $dest->{$k} = $source->{$k};
1451 my $snapshot_prepare = sub {
1452 my ($vmid, $snapname, $comment) = @_;
1456 my $updatefn = sub {
1458 my $conf = load_config
($vmid);
1460 die "you can't take a snapshot if it's a template\n"
1461 if is_template
($conf);
1465 $conf->{lock} = 'snapshot';
1467 die "snapshot name '$snapname' already used\n"
1468 if defined($conf->{snapshots
}->{$snapname});
1470 my $storecfg = PVE
::Storage
::config
();
1471 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1473 $snap = $conf->{snapshots
}->{$snapname} = {};
1475 &$snapshot_copy_config($conf, $snap);
1477 $snap->{'snapstate'} = "prepare";
1478 $snap->{'snaptime'} = time();
1479 $snap->{'description'} = $comment if $comment;
1480 $conf->{snapshots
}->{$snapname} = $snap;
1482 PVE
::LXC
::write_config
($vmid, $conf);
1485 lock_container
($vmid, 10, $updatefn);
1490 my $snapshot_commit = sub {
1491 my ($vmid, $snapname) = @_;
1493 my $updatefn = sub {
1495 my $conf = load_config
($vmid);
1497 die "missing snapshot lock\n"
1498 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1500 die "snapshot '$snapname' does not exist\n"
1501 if !defined($conf->{snapshots
}->{$snapname});
1503 die "wrong snapshot state\n"
1504 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1505 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1507 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1508 delete $conf->{lock};
1509 $conf->{parent
} = $snapname;
1511 PVE
::LXC
::write_config
($vmid, $conf);
1514 lock_container
($vmid, 10 ,$updatefn);
1518 my ($feature, $conf, $storecfg, $snapname) = @_;
1520 #Fixme add other drives if necessary.
1523 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1524 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $rootinfo->{volume
}, $snapname);
1526 return $err ?
0 : 1;
1529 sub snapshot_create
{
1530 my ($vmid, $snapname, $comment) = @_;
1532 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1534 my $conf = load_config
($vmid);
1536 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1537 my $running = check_running
($vmid);
1540 PVE
::Tools
::run_command
($cmd);
1543 my $storecfg = PVE
::Storage
::config
();
1544 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1545 my $volid = $rootinfo->{volume
};
1547 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1549 PVE
::Tools
::run_command
($cmd);
1552 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1553 &$snapshot_commit($vmid, $snapname);
1556 snapshot_delete
($vmid, $snapname, 1);
1561 sub snapshot_delete
{
1562 my ($vmid, $snapname, $force) = @_;
1568 my $updatefn = sub {
1570 $conf = load_config
($vmid);
1572 die "you can't delete a snapshot if vm is a template\n"
1573 if is_template
($conf);
1575 $snap = $conf->{snapshots
}->{$snapname};
1579 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1581 $snap->{snapstate
} = 'delete';
1583 PVE
::LXC
::write_config
($vmid, $conf);
1586 lock_container
($vmid, 10, $updatefn);
1588 my $storecfg = PVE
::Storage
::config
();
1590 my $del_snap = sub {
1594 if ($conf->{parent
} eq $snapname) {
1595 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1596 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1598 delete $conf->{parent
};
1602 delete $conf->{snapshots
}->{$snapname};
1604 PVE
::LXC
::write_config
($vmid, $conf);
1607 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1608 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1609 my $volid = $rootinfo->{volume
};
1612 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1616 if(!$err || ($err && $force)) {
1617 lock_container
($vmid, 10, $del_snap);
1619 die "Can't delete snapshot: $vmid $snapname $err\n";
1624 sub snapshot_rollback
{
1625 my ($vmid, $snapname) = @_;
1627 my $storecfg = PVE
::Storage
::config
();
1629 my $conf = load_config
($vmid);
1631 die "you can't rollback if vm is a template\n" if is_template
($conf);
1633 my $snap = $conf->{snapshots
}->{$snapname};
1635 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1637 my $rootfs = $snap->{rootfs
};
1638 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($rootfs);
1639 my $volid = $rootinfo->{volume
};
1641 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1643 my $updatefn = sub {
1645 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1646 if $snap->{snapstate
};
1650 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1652 die "unable to rollback vm $vmid: vm is running\n"
1653 if check_running
($vmid);
1655 $conf->{lock} = 'rollback';
1659 # copy snapshot config to current config
1661 my $tmp_conf = $conf;
1662 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1663 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1664 delete $conf->{snaptime
};
1665 delete $conf->{snapname
};
1666 $conf->{parent
} = $snapname;
1668 PVE
::LXC
::write_config
($vmid, $conf);
1671 my $unlockfn = sub {
1672 delete $conf->{lock};
1673 PVE
::LXC
::write_config
($vmid, $conf);
1676 lock_container
($vmid, 10, $updatefn);
1678 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1680 lock_container
($vmid, 5, $unlockfn);
1683 sub template_create
{
1684 my ($vmid, $conf) = @_;
1686 my $storecfg = PVE
::Storage
::config
();
1688 my $rootinfo = PVE
::LXC
::parse_ct_mountpoint
($conf->{rootfs
});
1689 my $volid = $rootinfo->{volume
};
1691 die "Template feature is not available for '$volid'\n"
1692 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1694 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1696 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1697 $rootinfo->{volume
} = $template_volid;
1698 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo);
1700 write_config
($vmid, $conf);
1706 return 1 if defined $conf->{template
} && $conf->{template
} == 1;