use strict;
use warnings;
+use Fcntl qw(O_RDONLY);
use PVE::AbstractConfig;
use PVE::Cluster qw(cfs_register_file);
use base qw(PVE::AbstractConfig);
+use constant {FIFREEZE => 0xc0045877,
+ FITHAW => 0xc0045878};
+
my $nodename = PVE::INotify::nodename();
my $lock_handles = {};
my $lockdir = "/run/lock/lxc";
die "implement me - snapshot_save_vmstate\n";
}
+sub __snapshot_activate_storages {
+ my ($class, $conf, $include_vmstate) = @_;
+
+ my $storecfg = PVE::Storage::config();
+ my $opts = $include_vmstate ? { 'extra_keys' => ['vmstate'] } : {};
+ my $storage_hash = {};
+
+ $class->foreach_volume_full($conf, $opts, sub {
+ my ($vs, $mountpoint) = @_;
+
+ return if $mountpoint->{type} ne 'volume';
+
+ my ($storeid) = PVE::Storage::parse_volume_id($mountpoint->{volume});
+ $storage_hash->{$storeid} = 1;
+ });
+
+ PVE::Storage::activate_storage_list($storecfg, [ sort keys $storage_hash->%* ]);
+}
+
sub __snapshot_check_running {
my ($class, $vmid) = @_;
return PVE::LXC::check_running($vmid);
return ($ret, $ret);
}
+# implements similar functionality to fsfreeze(8)
+sub fsfreeze_mountpoint {
+ my ($path, $thaw) = @_;
+
+ my $op = $thaw ? 'thaw' : 'freeze';
+ my $ioctl = $thaw ? FITHAW : FIFREEZE;
+
+ sysopen my $fd, $path, O_RDONLY or die "failed to open $path: $!\n";
+ my $ioctl_err;
+ if (!ioctl($fd, $ioctl, 0)) {
+ $ioctl_err = "$!";
+ }
+ close($fd);
+ die "fs$op '$path' failed - $ioctl_err\n" if defined $ioctl_err;
+}
+
sub __snapshot_freeze {
my ($class, $vmid, $unfreeze) = @_;
+ my $conf = $class->load_config($vmid);
+ my $storagecfg = PVE::Storage::config();
+
+ my $freeze_mps = [];
+ $class->foreach_volume($conf, sub {
+ my ($ms, $mountpoint) = @_;
+
+ return if $mountpoint->{type} ne 'volume';
+
+ if (PVE::Storage::volume_snapshot_needs_fsfreeze($storagecfg, $mountpoint->{volume})) {
+ push @$freeze_mps, $mountpoint->{mp};
+ }
+ });
+
+ my $freeze_mountpoints = sub {
+ my ($thaw) = @_;
+
+ return if scalar(@$freeze_mps) == 0;
+
+ my $pid = PVE::LXC::find_lxc_pid($vmid);
+
+ for my $mp (@$freeze_mps) {
+ eval{ fsfreeze_mountpoint("/proc/${pid}/root/${mp}", $thaw); };
+ warn $@ if $@;
+ }
+ };
+
if ($unfreeze) {
eval { PVE::LXC::thaw($vmid); };
warn $@ if $@;
+ $freeze_mountpoints->(1);
} else {
PVE::LXC::freeze($vmid);
PVE::LXC::sync_container_namespace($vmid);
+ $freeze_mountpoints->(0);
}
}
delete $snap->{$remove_drive};
$class->add_unused_volume($snap, $mountpoint->{volume})
- if ($mountpoint->{type} eq 'volume');
+ if $mountpoint && ($mountpoint->{type} eq 'volume');
}
}
}
sub __snapshot_rollback_vol_possible {
- my ($class, $mountpoint, $snapname) = @_;
+ my ($class, $mountpoint, $snapname, $blockers) = @_;
my $storecfg = PVE::Storage::config();
- PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
+ PVE::Storage::volume_rollback_is_possible(
+ $storecfg,
+ $mountpoint->{volume},
+ $snapname,
+ $blockers,
+ );
}
sub __snapshot_rollback_vol_rollback {
cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
-my $valid_mount_option_re = qr/(noatime|nodev|nosuid|noexec)/;
+my $valid_mount_option_re = qr/(noatime|lazytime|nodev|nosuid|noexec)/;
sub is_valid_mount_option {
my ($option) = @_;
optional => 1,
});
+# IP address with optional interface suffix for link local ipv6 addresses
+PVE::JSONSchema::register_format('lxc-ip-with-ll-iface', \&verify_ip_with_ll_iface);
+sub verify_ip_with_ll_iface {
+ my ($addr, $noerr) = @_;
+
+ if (my ($addr, $iface) = ($addr =~ /^(fe80:[^%]+)%(.*)$/)) {
+ if (PVE::JSONSchema::pve_verify_ip($addr, 1)
+ && PVE::JSONSchema::pve_verify_iface($iface, 1))
+ {
+ return $addr;
+ }
+ }
+
+ return PVE::JSONSchema::pve_verify_ip($addr, $noerr);
+}
+
+
my $features_desc = {
mount => {
optional => 1,
ostype => {
optional => 1,
type => 'string',
- enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
+ enum => [qw(debian devuan ubuntu centos fedora opensuse archlinux alpine gentoo nixos unmanaged)],
description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
},
console => {
type => 'integer',
description => "The number of cores assigned to the container. A container can use all available cores by default.",
minimum => 1,
- maximum => 128,
+ maximum => 8192,
},
cpulimit => {
optional => 1,
type => 'number',
description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
minimum => 0,
- maximum => 128,
+ maximum => 8192,
default => 0,
},
cpuunits => {
description => {
optional => 1,
type => 'string',
- description => "Container description. Only used on the configuration web interface.",
+ description => "Description for the Container. Shown in the web-interface CT's summary."
+ ." This is saved as comment inside the configuration file.",
+ maxLength => 1024 * 8,
},
searchdomain => {
optional => 1,
},
nameserver => {
optional => 1,
- type => 'string', format => 'address-list',
+ type => 'string', format => 'lxc-ip-with-ll-iface-list',
description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
},
timezone => {
description => 'Tags of the Container. This is only meta information.',
optional => 1,
},
+ debug => {
+ optional => 1,
+ type => 'boolean',
+ description => "Try to be more verbose. For now this only enables debug log-level on start.",
+ default => 0,
+ },
};
my $valid_lxc_conf_keys = {
};
PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
-my $MAX_LXC_NETWORKS = 10;
+my $MAX_LXC_NETWORKS = 32;
for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
$confdesc->{"net$i"} = {
optional => 1,
$confdesc->{"mp$i"} = {
optional => 1,
type => 'string', format => $mp_desc,
- description => "Use volume as container mount point.",
+ description => "Use volume as container mount point. Use the special " .
+ "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.",
optional => 1,
};
}
}
sub parse_pct_config {
- my ($filename, $raw) = @_;
+ my ($filename, $raw, $strict) = @_;
return undef if !defined($raw);
pending => {},
};
+ my $handle_error = sub {
+ my ($msg) = @_;
+
+ if ($strict) {
+ die $msg;
+ } else {
+ warn $msg;
+ }
+ };
+
$filename =~ m|/lxc/(\d+).conf$|
|| die "got strange filename '$filename'";
if ($validity eq 1) {
push @{$conf->{lxc}}, [$key, $value];
} elsif (my $errmsg = $validity) {
- warn "vm $vmid - $key: $errmsg\n";
+ $handle_error->("vm $vmid - $key: $errmsg\n");
} else {
- warn "vm $vmid - unable to parse config: $line\n";
+ $handle_error->("vm $vmid - unable to parse config: $line\n");
}
} elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
$descr .= PVE::Tools::decode_text($2);
if ($section eq 'pending') {
$conf->{delete} = $value;
} else {
- warn "vm $vmid - property 'delete' is only allowed in [pve:pending]\n";
+ $handle_error->("vm $vmid - property 'delete' is only allowed in [pve:pending]\n");
}
- } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
+ } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
my $key = $1;
my $value = $2;
eval { $value = PVE::LXC::Config->check_type($key, $value); };
- warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
+ $handle_error->("vm $vmid - unable to parse value of '$key' - $@") if $@;
$conf->{$key} = $value;
} else {
- warn "vm $vmid - unable to parse config: $line\n";
+ $handle_error->("vm $vmid - unable to parse config: $line\n");
}
}
return $parse_ct_mountpoint_full->($class, $unused_desc, $volume_string, $noerr);
}
- die "parse_volume - unknown type: $key\n";
+ die "parse_volume - unknown type: $key\n" if !$noerr;
+
+ return;
}
sub print_volume {
} elsif ($opt eq 'swap') {
$hotplug_memory->(undef, 0);
} elsif ($opt eq 'cpulimit') {
- $cgroup->change_cpu_quota(-1, 100000);
+ $cgroup->change_cpu_quota(undef, undef); # reset, cgroup module can better decide values
} elsif ($opt eq 'cpuunits') {
$cgroup->change_cpu_shares(undef, $confdesc->{cpuunits}->{default});
} elsif ($opt =~ m/^net(\d)$/) {
return $reverse ? reverse @names : @names;
}
+sub valid_volume_keys_with_unused {
+ my ($class, $reverse) = @_;
+ my @names = $class->valid_volume_keys();
+ for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
+ push @names, "unused$i";
+ }
+ return $reverse ? reverse @names : @names;
+}
+
sub get_vm_volumes {
my ($class, $conf, $excludes) = @_;