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";
sub mountpoint_backup_enabled {
my ($class, $mp_key, $mountpoint) = @_;
- return 1 if $mp_key eq 'rootfs';
-
- return 0 if $mountpoint->{type} ne 'volume';
-
- return 1 if $mountpoint->{backup};
-
- return 0;
+ my $enabled;
+ my $reason;
+
+ if ($mp_key eq 'rootfs') {
+ $enabled = 1;
+ $reason = 'rootfs';
+ } elsif ($mountpoint->{type} ne 'volume') {
+ $enabled = 0;
+ $reason = 'not a volume';
+ } elsif ($mountpoint->{backup}) {
+ $enabled = 1;
+ $reason = 'enabled';
+ } else {
+ $enabled = 0;
+ $reason = 'disabled';
+ }
+ return wantarray ? ($enabled, $reason) : $enabled;
}
sub has_feature {
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::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
+ eval { PVE::LXC::thaw($vmid); };
warn $@ if $@;
+ $freeze_mountpoints->(1);
} else {
- PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
+ 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 => {
+ optional => 1,
+ type => 'string', format => 'pve-ct-timezone',
+ description => "Time zone to use in the container. If option isn't set, then nothing will be done. Can be set to 'host' to match the host time zone, or an arbitrary time zone option from /usr/share/zoneinfo/zone.tab",
+ },
rootfs => get_standard_option('pve-ct-rootfs'),
parent => {
optional => 1,
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 = {
}
my $validity = $valid_lxc_conf_keys->{$key};
return $validity if defined($validity);
- return 1 if $key =~ /^lxc\.cgroup\./ # allow all cgroup values
+ return 1 if $key =~ /^lxc\.cgroup2?\./ # allow all cgroup values
|| $key =~ /^lxc\.prlimit\./ # allow all prlimits
|| $key =~ /^lxc\.net\./; # allow custom network definitions
return 0;
};
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,
};
}
+PVE::JSONSchema::register_format('pve-ct-timezone', \&verify_ct_timezone);
+sub verify_ct_timezone {
+ my ($timezone, $noerr) = @_;
+
+ return if $timezone eq 'host'; # using host settings
+
+ PVE::JSONSchema::pve_verify_timezone($timezone);
+}
+
PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
sub verify_lxc_mp_string {
my ($mp, $noerr) = @_;
$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 {
$errors->{$opt} = "unable to hotplug $opt: $msg";
};
- my $changes;
foreach my $opt (sort keys %{$conf->{pending}}) { # add/change
next if $selection && !$selection->{$opt};
if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
$conf->{$opt} = delete $conf->{pending}->{$opt};
- $changes = 1;
}
}
- if ($changes) {
- $class->write_config($vmid, $conf);
- }
-
my $cgroup = PVE::LXC::CGroup->new($vmid);
# There's no separate swap size to configure, there's memory and "total"
} 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)$/) {
die "skip\n";
}
+ if (exists($conf->{$opt})) {
+ die "skip\n"; # don't try to hotplug over existing mp
+ }
+
$class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1);
# apply_pending_mountpoint modifies the value if it creates a new disk
$value = $conf->{pending}->{$opt};
delete $conf->{pending}->{$opt};
}
}
-
- $class->write_config($vmid, $conf);
}
sub vmconfig_apply_pending {
$conf->{$opt} = delete $conf->{pending}->{$opt};
}
}
-
- $class->write_config($vmid, $conf);
}
my $rescan_volume = sub {
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) = @_;
return $volhash;
}
+sub get_backup_volumes {
+ my ($class, $conf) = @_;
+
+ my $return_volumes = [];
+
+ my $test_mountpoint = sub {
+ my ($key, $volume) = @_;
+
+ my ($included, $reason) = $class->mountpoint_backup_enabled($key, $volume);
+
+ push @$return_volumes, {
+ key => $key,
+ included => $included,
+ reason => $reason,
+ volume_config => $volume,
+ };
+ };
+
+ PVE::LXC::Config->foreach_volume($conf, $test_mountpoint);
+
+ return $return_volumes;
+}
+
1;