cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
-PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
-sub verify_lxc_network {
- my ($value, $noerr) = @_;
-
- return $value if parse_lxc_network($value);
-
- return undef if $noerr;
-
- die "unable to parse network setting\n";
-}
-
-PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint);
-sub verify_ct_mountpoint {
- my ($value, $noerr) = @_;
-
- return $value if parse_ct_mountpoint($value);
-
- return undef if $noerr;
-
- die "unable to parse CT mountpoint options\n";
-}
+my $rootfs_desc = {
+ volume => {
+ type => 'string',
+ default_key => 1,
+ format_description => 'volume',
+ description => 'Volume, device or directory to mount into the container.',
+ },
+ backup => {
+ type => 'boolean',
+ format_description => '[1|0]',
+ description => 'Whether to include the mountpoint in backups.',
+ optional => 1,
+ },
+ size => {
+ type => 'string',
+ format_description => 'DiskSize',
+ pattern => '\d+[TGMK]?',
+ description => 'Volume size (read only value).',
+ optional => 1,
+ },
+};
PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
- type => 'string', format => 'pve-ct-mountpoint',
- typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
+ type => 'string', format => $rootfs_desc,
description => "Use volume as container root.",
optional => 1,
});
hostname => {
optional => 1,
description => "Set a host name for the container.",
- type => 'string',
+ type => 'string', format => 'dns-name',
maxLength => 255,
},
description => {
},
searchdomain => {
optional => 1,
- type => 'string',
+ type => 'string', format => 'dns-name-list',
description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
},
nameserver => {
optional => 1,
- type => 'string',
+ type => 'string', format => 'address-list',
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.",
},
rootfs => get_standard_option('pve-ct-rootfs'),
protection => {
optional => 1,
type => 'boolean',
- description => "Sets the protection flag of the container. This will prevent the remove operation.",
+ description => "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
default => 0,
},
};
'lxc.' => 1,
};
+my $netconf_desc = {
+ type => {
+ type => 'string',
+ optional => 1,
+ description => "Network interface type.",
+ enum => [qw(veth)],
+ },
+ name => {
+ type => 'string',
+ format_description => 'String',
+ description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
+ pattern => '[-_.\w\d]+',
+ },
+ bridge => {
+ type => 'string',
+ format_description => 'vmbr<Number>',
+ description => 'Bridge to attach the network device to.',
+ pattern => '[-_.\w\d]+',
+ },
+ hwaddr => {
+ type => 'string',
+ format_description => 'MAC',
+ description => 'Bridge to attach the network device to. (lxc.network.hwaddr)',
+ pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
+ optional => 1,
+ },
+ mtu => {
+ type => 'integer',
+ format_description => 'Number',
+ description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
+ optional => 1,
+ },
+ ip => {
+ type => 'string',
+ format => 'pve-ipv4-config',
+ format_description => 'IPv4Format/CIDR',
+ description => 'IPv4 address in CIDR format.',
+ optional => 1,
+ },
+ gw => {
+ type => 'string',
+ format => 'ipv4',
+ format_description => 'GatewayIPv4',
+ description => 'Default gateway for IPv4 traffic.',
+ optional => 1,
+ },
+ ip6 => {
+ type => 'string',
+ format => 'pve-ipv6-config',
+ format_description => 'IPv6Format/CIDR',
+ description => 'IPv6 address in CIDR format.',
+ optional => 1,
+ },
+ gw6 => {
+ type => 'string',
+ format => 'ipv6',
+ format_description => 'GatewayIPv6',
+ description => 'Default gateway for IPv6 traffic.',
+ optional => 1,
+ },
+ firewall => {
+ type => 'boolean',
+ format_description => '[1|0]',
+ description => "Controls whether this interface's firewall rules should be used.",
+ optional => 1,
+ },
+ tag => {
+ type => 'integer',
+ format_description => 'VlanNo',
+ minimum => '2',
+ maximum => '4094',
+ description => "VLAN tag foro this interface.",
+ optional => 1,
+ },
+};
+PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
+
my $MAX_LXC_NETWORKS = 10;
for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
$confdesc->{"net$i"} = {
optional => 1,
- type => 'string', format => 'pve-lxc-network',
- description => "Specifies network interfaces for the container.\n\n".
- "The string should have the follow format:\n\n".
- "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
- "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
- ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
- ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
+ type => 'string', format => $netconf_desc,
+ description => "Specifies network interfaces for the container.",
};
}
+my $mp_desc = {
+ %$rootfs_desc,
+ mp => {
+ type => 'string',
+ format_description => 'Path',
+ description => 'Path to the mountpoint as seen from inside the container.',
+ optional => 1,
+ },
+};
+PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
+
my $MAX_MOUNT_POINTS = 10;
for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
$confdesc->{"mp$i"} = {
optional => 1,
- type => 'string', format => 'pve-ct-mountpoint',
- typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
+ type => 'string', format => $mp_desc,
description => "Use volume as container mount point (experimental feature).",
optional => 1,
};
$descr .= PVE::Tools::decode_text($2);
} elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
$conf->{snapstate} = $1;
- } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
+ } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
my $key = $1;
my $value = $2;
eval { $value = check_type($key, $value); };
my $d = $list->{$vmid};
next if $d->{status} ne 'running';
- $d->{uptime} = 100; # fixme:
+ my $pid = find_lxc_pid($vmid);
+ my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
+ $d->{uptime} = time - $ctime; # the method lxcfs uses
$d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
$d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
$data //= '';
- my $res = {};
-
- foreach my $p (split (/,/, $data)) {
- next if $p =~ m/^\s*$/;
-
- if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
- my ($k, $v) = ($1, $2);
- return undef if defined($res->{$k});
- $res->{$k} = $v;
- } else {
- if (!$res->{volume} && $p !~ m/=/) {
- $res->{volume} = $p;
- } else {
- return undef;
- }
- }
+ my $res;
+ eval { $res = PVE::JSONSchema::parse_property_string($mp_desc, $data) };
+ if ($@) {
+ warn $@;
+ return undef;
}
return undef if !defined($res->{volume});
- return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
-
if ($res->{size}) {
return undef if !defined($res->{size} = &$parse_size($res->{size}));
}
return $res if !$data;
- foreach my $pv (split (/,/, $data)) {
- if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
- $res->{$1} = $2;
- } else {
- return undef;
- }
+ eval { $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data) };
+ if ($@) {
+ warn $@;
+ return undef;
}
$res->{type} = 'veth';
die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
}
+sub check_protection {
+ my ($vm_conf, $err_msg) = @_;
+
+ if ($vm_conf->{protection}) {
+ die "$err_msg - protection mode enabled\n";
+ }
+}
+
sub update_lxc_config {
my ($storage_cfg, $vmid, $conf) = @_;
my $ttycount = get_tty_count($conf);
$raw .= "lxc.tty = $ttycount\n";
+ # some init scripts expects a linux terminal (turnkey).
+ $raw .= "lxc.environment = TERM=linux\n";
+
my $utsname = $conf->{hostname} || "CT$vmid";
$raw .= "lxc.utsname = $utsname\n";
} elsif ($opt eq 'protection') {
delete $conf->{$opt};
} elsif ($opt =~ m/^mp(\d+)$/) {
+ check_protection($conf, "can't remove CT $vmid drive '$opt'");
delete $conf->{$opt};
push @nohotplug, $opt;
next if $running;
- } elsif ($opt eq 'rootfs') {
- die "implement me"
} else {
die "implement me"
}
} elsif ($opt eq 'protection') {
$conf->{$opt} = $value ? 1 : 0;
} elsif ($opt =~ m/^mp(\d+)$/) {
+ check_protection($conf, "can't update CT $vmid drive '$opt'");
$conf->{$opt} = $value;
$new_disks = 1;
push @nohotplug, $opt;
next;
} elsif ($opt eq 'rootfs') {
+ check_protection($conf, "can't update CT $vmid drive '$opt'");
die "implement me: $opt";
} else {
die "implement me: $opt";
foreach_mountpoint($conf, sub {
my ($ms, $mountpoint) = @_;
+
+ # skip bind mounts and block devices
+ if ($mountpoint->{volume} =~ m|^/|) {
+ return;
+ }
+
my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
});
my $conf = load_config($vmid);
- my $cmd = "/usr/bin/lxc-freeze -n $vmid";
my $running = check_running($vmid);
eval {
if ($running) {
- PVE::Tools::run_command($cmd);
+ PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
+ PVE::Tools::run_command(['/bin/sync']);
};
my $storecfg = PVE::Storage::config();
my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
my $volid = $rootinfo->{volume};
- $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
if ($running) {
- PVE::Tools::run_command($cmd);
+ PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
};
PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
if ($scfg->{type} eq 'zfspool') {
my $path_arg = $path;
$path_arg =~ s!^/+!!;
- PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
+ PVE::Tools::run_command(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
} else {
die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
}
}
if ($mount_path) {
if ($isBase || defined($snapname)) {
- PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
+ PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
} else {
PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
}