Commit 2202163e authored by Mike Hibler's avatar Mike Hibler

Sort out ZFS refquota/quota settings, part 2.

Add setzfsquotas script to handle fixup of existing quotas, add update
script to do a one-time invocation of this script at boss-install time,
and fix accountsetup so it will properly set both quotas going forward.
parent 2c6695d9
#!/usr/bin/perl -w #!/usr/bin/perl -w
# #
# Copyright (c) 2010-2016 University of Utah and the Flux Group. # Copyright (c) 2010-2017 University of Utah and the Flux Group.
# #
# {{{GENIPUBLIC-LICENSE # {{{GENIPUBLIC-LICENSE
# #
...@@ -75,6 +75,9 @@ my $ZFS_ROOT = "@ZFS_ROOT@"; ...@@ -75,6 +75,9 @@ my $ZFS_ROOT = "@ZFS_ROOT@";
my $ZFS_QUOTA_USER = "@ZFS_QUOTA_USER@"; my $ZFS_QUOTA_USER = "@ZFS_QUOTA_USER@";
my $ZFS_QUOTA_PROJECT = "@ZFS_QUOTA_PROJECT@"; my $ZFS_QUOTA_PROJECT = "@ZFS_QUOTA_PROJECT@";
my $ZFS_QUOTA_GROUP = "@ZFS_QUOTA_GROUP@"; my $ZFS_QUOTA_GROUP = "@ZFS_QUOTA_GROUP@";
my $ZFS_QUOTA_USER_X = "@ZFS_QUOTA_USER_X@";
my $ZFS_QUOTA_PROJECT_X = "@ZFS_QUOTA_PROJECT_X@";
my $ZFS_QUOTA_GROUP_X = "@ZFS_QUOTA_GROUP_X@";
my $PW = "/usr/sbin/pw"; my $PW = "/usr/sbin/pw";
my $USERADD = "/usr/sbin/pw useradd"; my $USERADD = "/usr/sbin/pw useradd";
my $USERDEL = "/usr/sbin/pw userdel"; my $USERDEL = "/usr/sbin/pw userdel";
...@@ -832,12 +835,22 @@ sub MakeDir($$) ...@@ -832,12 +835,22 @@ sub MakeDir($$)
$path = "${ZFS_ROOT}${fs}/$dir"; $path = "${ZFS_ROOT}${fs}/$dir";
# XXX quotas # XXX quotas
my ($refquota,$mult);
if ($fs eq $USERROOT) { if ($fs eq $USERROOT) {
$cmdarg = "-o quota=$ZFS_QUOTA_USER"; $refquota = $ZFS_QUOTA_USER;
$mult = $ZFS_QUOTA_USER_X;
} elsif ($fs eq $PROJROOT) { } elsif ($fs eq $PROJROOT) {
$cmdarg = "-o quota=$ZFS_QUOTA_PROJECT"; $refquota = $ZFS_QUOTA_PROJECT;
$mult = $ZFS_QUOTA_PROJECT_X;
} elsif ($fs eq $GROUPROOT) { } elsif ($fs eq $GROUPROOT) {
$cmdarg = "-o quota=$ZFS_QUOTA_GROUP"; $refquota = $ZFS_QUOTA_GROUP;
$mult = $ZFS_QUOTA_GROUP_X;
}
if (defined($refquota) && $refquota =~ /^(\d+(?:\.\d+)?)([MGT]?)$/) {
my ($num,$unit) = ($1,$2);
$unit = "" if (!defined($unit));
$num = sprintf "%.1f", $num * $mult;
$cmdarg = "-o refquota=$refquota -o quota=$num$unit";
} else { } else {
$cmdarg = ""; $cmdarg = "";
} }
......
#
# Fix up ZFS quota settings. We should be using 'refquota' for the amount
# that users can access, and 'quota' to allow additional room for snapshots
# This (refquota non-zero and less than quota) only matters on the mothership
# right now where we are doing ZFS snapshots for backups, but may matter on
# other sites later.
#
use strict;
use libinstall;
use installvars;
sub InstallUpdate($$)
{
my ($version, $phase) = @_;
#
# If something should run in the pre-install phase.
#
if ($phase eq "pre") {
;
}
#
# If something should run in the post-install phase.
#
if ($phase eq "post") {
Phase "zfs-quotas", "Fixing ZFS quotas", sub {
my $cmd = "$SSH -o 'BatchMode=yes' root\@${USERNODE} ".
"$TBROOT/sbin/setzfsquotas -a";
ExecQuietFatal($cmd);
};
}
return 0;
}
1;
# Local Variables:
# mode:perl
# End:
...@@ -70,7 +70,8 @@ LIBEXEC_SCRIPTS = spewleds webcopy spewsource webcvsweb xlogin webviewvc \ ...@@ -70,7 +70,8 @@ LIBEXEC_SCRIPTS = spewleds webcopy spewsource webcvsweb xlogin webviewvc \
$(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS) $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
CTRLSBIN_SCRIPTS= opsdb_control.proxy daemon_wrapper ec2import.proxy \ CTRLSBIN_SCRIPTS= opsdb_control.proxy daemon_wrapper ec2import.proxy \
ec2import-image.pl GrubConf.rb export-template-remote.rb ec2import-image.pl GrubConf.rb export-template-remote.rb \
setzfsquotas
SBSBIN_SCRIPTS= daemon_wrapper subboss_cacheclean SBSBIN_SCRIPTS= daemon_wrapper subboss_cacheclean
......
#!/usr/bin/perl -w
#
# Copyright (c) 2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
#
# Ensure that individual /users, /proj, /groups and /scratch filesystem
# quotas and refquotas are set correctly.
#
# If refquota is not set but quota is, we set refquota to the current
# quota value and apply the mulitplier to set a new value for quota.
# If there was no current quota, it stays that way.
#
# Currently, if refquota is set but not to the target value, we just
# warn and leave it as is. We might want an option in the future to
# override that.
#
# If the new calculated (from refquota) value for quota is smaller than
# the current quota value, we just warn and leave it along. Again, we
# might want a force option. If the calculated value is larger than the
# current quota, we warn but also increase the value.
#
# The net effect is that every filesystem will have a quota that is at
# least N (where N is the multiplication factor) times larger than the
# refquota value. However, right now either or both of the refquota and
# quota values might be different than the target values.
#
use English;
use Getopt::Std;
sub usage()
{
print STDERR "Usage: setzfsquotas [-adhqnr] [-Q value] [-F factor] FS ...\n";
print STDERR "\nSet ZFS quota/refquota as indicated for each specified\n";
print STDERR "standard Emulab filesystem\n";
print STDERR " -h This message\n";
print STDERR " -a Set quotas for all standard Emulab filesystems\n";
print STDERR " Implies '-r'\n";
print STDERR " -d Enabled additional debugging messages\n";
print STDERR " -q Run quietly\n";
print STDERR " -n Don't change quotas, just say what would happen\n";
print STDERR " -r Recursive; assume arg is a root and apply to all FSes underneath\n";
print STDERR " -Q value Set the quota to the indicated value.\n";
print STDERR " Value should be a valid string for the ZFS refquota attribute.\n";
print STDERR " Default comes from the defs- file ZFS_QUOTA_* variables\n";
print STDERR " -F factor Multiplier for setting the quota attribute.\n";
print STDERR " 1.0 will set quota==refquota. Larger values will permit space\n";
print STDERR " in excess of what the user can allocate (i.e., for backup purposes)\n";
print STDERR " Default comes from the defs- file ZFS_QUOTA_*_X variables\n";
print STDERR "\nFS should be one of more of 'users', 'proj', 'groups'.\n";
print STDERR "Not required if '-a' is specified.\n";
}
my $optlist = "adhnqrF:Q:";
my $doall = 0;
my $debug = 0;
my $quiet = 0;
my $quotaval;
my $factor;
my $recursive = 0;
my $impotent = 0;
#
# Configure variables
#
my $TB = "@prefix@";
my $WITHZFS = "@WITHZFS@";
my $ZFS_ROOT = "@ZFS_ROOT@";
my $U_QUOTA = "@ZFS_QUOTA_USER@";
my $P_QUOTA = "@ZFS_QUOTA_PROJECT@";
my $G_QUOTA = "@ZFS_QUOTA_GROUP@";
my $U_MULT = "@ZFS_QUOTA_USER_X@";
my $P_MULT = "@ZFS_QUOTA_PROJECT_X@";
my $G_MULT = "@ZFS_QUOTA_GROUP_X@";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Protos
sub setquotas($$$);
sub getquotas($);
sub getsubfses($$);
sub parsenumstr($);
#
# Turn off line buffering on output
#
$| = 1;
# Globals
my @fses = ();
my %fsquotas = (
"users" => parsenumstr($U_QUOTA),
"proj" => parsenumstr($P_QUOTA),
"groups" => parsenumstr($G_QUOTA)
);
my %fsmults = (
"users" => $U_MULT,
"proj" => $P_MULT,
"groups" => $G_MULT
);
my %fsinfo = ();
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
exit(1);
}
if (defined($options{'h'})) {
usage();
exit(0);
}
if (defined($options{'d'})) {
$debug++;
}
if (defined($options{"a"})) {
$doall = 1;
$recursive = 1;
}
if (defined($options{"r"})) {
$recursive = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
if (defined($options{"Q"})) {
$quotaval = parsenumstr($options{"Q"});
}
if (defined($options{'F'})) {
if ($options{'F'} =~ /^(\d+(\.\d+)?)$/) {
$factor = $1;
if ($factor < 1.0) {
print STDERR "*** Factor must be at least 1\n";
usage();
exit(1);
}
} else {
print STDERR "*** Factor must be a number.\n";
usage();
exit(1);
}
}
if ($doall) {
@fses = ("users", "proj", "groups");
} else {
if (@ARGV == 0) {
print STDERR "*** Must specify one or more FSes\n";
usage();
exit(1);
}
@fses = @ARGV;
}
# Set this to turn off tblog in libraries.
$ENV{'TBLOG_OFF'} = "yep";
# Load the Testbed support stuff.
#use lib "@prefix@/lib";
use lib "/usr/testbed/lib";
use libtestbed;
if (!$impotent && $UID) {
print STDERR "Must run as root.\n";
exit(1);
}
#
# Handle recursive traversal
#
my $rv = 0;
if ($recursive) {
my @allfs = ();
foreach my $fs (@fses) {
print "Finding sub FSes of $fs ...\n"
if (!$quiet);
if (!exists($fsquotas{$fs})) {
print STDERR "$fs: invalid root filesystem!\n";
$rv++;
next;
}
push @allfs, getsubfses($fs, "$ZFS_ROOT/$fs");
}
@fses = @allfs;
}
foreach my $fs (sort @fses) {
print "Processing $fs ...\n"
if (!$quiet);
my ($rq, $q, $desq, $mult) = getquotas($fs);
if (!defined($rq)) {
print STDERR "$fs: not a ZFS filesystem?\n";
$rv++;
next;
}
print STDERR "$fs: refquota='$rq', quota='$q', desquota='$desq', mult='$mult'\n"
if ($debug);
# if no quotas, nothing changes
if ($rq == 0 && $q == 0) {
print "$fs: no quotas set, not modified.\n"
if (!$quiet);
next;
}
my ($newrq, $newq);
# refquota is zero, set to quota value and calculate new quota value
if ($rq == 0) {
$newrq = $q;
$newq = $newrq * $mult;
}
# refquota set to correct value, make sure quota is big enough
elsif ($rq == $desq) {
$newrq = $rq;
$newq = $rq * $mult;
if ($q == 0 || $newq == $q) {
print "$fs: refquota and quota correct, not modified\n"
if (!$quiet);
next;
}
if ($newq < $q) {
print "$fs: WARNING: quota is larger than desired ($q > $newq), not modified\n"
if (!$quiet);
next;
}
}
# refquota set to a different value, warn and make sure quota is big enough
else {
$newrq = $rq;
$newq = $rq * $mult;
if ($q == 0 || $newq <= $q) {
print "$fs: WARNING: refquota not at desired value ($rq != $desq), not modified\n"
if (!$quiet);
next;
}
}
# if we get here, we need to change something
my $msg = "";
if ($rq != $newrq) {
$msg .= "fixing refquota ($rq->$newrq)";
} else {
$newrq = 0;
}
if ($q != $newq) {
if ($msg) {
$msg .= " and quota ($q->$newq)";
} else {
$msg .= "fixing quota ($q->$newq)";
}
} else {
$newq = 0;
}
print "$fs: $msg\n"
if ($impotent || !$quiet);
$rv += setquotas($fs, $newrq, $newq);
}
exit($rv);
sub setquotas($$$)
{
my ($fs,$refq,$q) = @_;
my $cmd;
if ($impotent) {
$cmd = "echo ";
}
$cmd .= "/sbin/zfs";
my $rv = 0;
if ($refq && system("$cmd set refquota=$refq $fs")) {
print STDERR "*** $fs: could not set refquota!\n";
$rv++;
}
if ($q && system("$cmd set quota=$q $fs")) {
print STDERR "*** $fs: could not set quota!\n";
$rv++;
}
return $rv;
}
sub getquotas($)
{
my ($fs) = @_;
my ($refq,$q,$tgtrefq,$tgtmult);
if (exists($fsinfo{$fs})) {
$refq = $fsinfo{$fs}{'refquota'};
$q =$fsinfo{$fs}{'quota'};
$tgtrefq = $fsquotas{$fsinfo{$fs}{'root'}};
$tgtmult = $fsmults{$fsinfo{$fs}{'root'}};
} else {
my $output = `/sbin/zfs list -Hpo refquota,quota $fs 2>/dev/null`;
if ($?) {
print STDERR "*** $fs: could not get attributes\n";
return undef;
}
chomp $output;
($refq, $q) = split '\s+', $output;
my $root;
if ($fs =~ /^$ZFS_ROOT\/([^\/]+)\//) {
$root = $1;
} else {
$root = "unknown";
}
if (defined($quotaval)) {
$tgtrefq = $quotaval;
} elsif (exists($fsquotas{$root})) {
$tgtrefq = $fsquotas{$root};
} else {
print STDERR "*** $fs: unknown root FS '$root'\n";
return undef;
}
if (defined($factor)) {
$tgtmult = $factor;
} elsif (exists($fsmults{$root})) {
$tgtmult = $fsmults{$root};
} else {
print STDERR "*** $fs: unknown root FS '$root'\n";
return undef;
}
}
return ($refq, $q, $tgtrefq, $tgtmult);
}
#
# Get sub filesystems of one of the main mountpoints (e.g., "z/users").
#
sub getsubfses($$)
{
my ($root,$fs) = @_;
my @fslist = ();
my @output = `/sbin/zfs list -r -Hpo name,refquota,quota $fs 2>/dev/null`;
if ($?) {
print STDERR "*** $fs: could not find ZFS\n";
return ();
}
foreach my $line (@output) {
chomp $line;
my ($name,$refq,$q) = split '\s+', $line;
if ($name eq $fs) {
next;
}
push @fslist, $name;
$fsinfo{$name}{'refquota'} = $refq;
$fsinfo{$name}{'quota'} = $q;
$fsinfo{$name}{'root'} = $root;
}
return @fslist;
}
sub parsenumstr($)
{
my ($str) = @_;
my ($num,$unit);
if ($str =~ /^(\d+(?:\.\d+)?)([MGT]?)$/) {
($num,$unit) = ($1,$2);
if ($unit eq "M") {
$num *= (1024*1024);
} elsif ($unit eq "G") {
$num *= (1024*1024*1024);
} elsif ($unit eq "T") {
$num *= (1024*1024*1024*1024);
} else {
die "*** could not parse '$str' as a number\n";
}
} elsif ($str eq "none") {
$num = 0;
} else {
die "*** could not parse '$str' as a number\n";
}
return $num;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment