#!/usr/bin/perl # docelic, Sat Dec 22 18:35:37 EST 2007 # Script warns people when they're approaching quota. The purpose # is not to 'behave' users but to give them a notice they should # fill quota increase request. (We want to prevent people from # ever actually running into quota limits, especially for Mail # and Database volumes. We use quotas to keep the shop clean, not # to impose restrictions). # Script retrieves volume info from deleuze, from there it # reads mail., user. and db. volumes into %vol, then # sees which volumes have > $threshold usage, relative to volume # quota. # # For volumes above threshold, it extracts the owner name from # volume name (like, mail.USERNAME) and saves info to %warn. # # vos listvol is parsed into structure like this: # # $vol{user.docelic}{diskused} = 36718 ( All fields from the # output of vos listvol are loaded into the hash. list of # fields is at the bottom of this script ) # # people who are over quota are in %warn: # # $warn{docelic}{user} = 0.9 # $warn{docelic}{mail} = 0.74 # # "Offenders" are saved to $state{$user} = [ run_number, exp_inc ] # and removed when they resolve their quota situation. For this # to work (and emails to get sent), pass --auto (most certain when # running from cron). # # run_number is the run (tick) when they were first spotted to # be over safe value, and exp_inc is the interval at which they # need to be reminded. (We use 1, 2, 4, 8, 16...). Each 'tick' # and number usually refers to 1 day (if ran from cron daily) # In the current implimentation, folks will be reminded on # 1st, 3rd, 7th, 15th and 31st day after their quota overrun. # Then they will be given 2 months of no messages, and then # after 3 months all together, the "warning cycle" will start again. # - If user is over 0.96 on mail or db volume, we will add 2% to his # quota one-time to alleviate immediate problem. use warnings; use strict; use Storable qw/nstore retrieve/; use Fatal qw/open write read close/; use Data::Dumper qw/Dumper/; # Warn threshold our %threshold = ( user => 0.8, mail => 0.6, db => 0.4 ); # AFS servers with volumes our @servers = qw/deleuze/; # Which volumes we want? # (Your volumes need to be in format (TYPE).USERNAME, otherwise # you will have to modify below where we take .USERNAME to mean # person's USERNAME). Changing this will also trigger changes # in template email (see DATA below). our $volume_pattern = '(mail|user|db)\.[a-zA-Z0-9]+'; # implicit ^...$ # Cache file our $statefile = '/var/cache/hcoop/quotacheck'; # Hash structure containing load of $statefile our %state; # Update cache & send out emails (--auto), or just run terminal report our $auto = grep {/^--auto$/} @ARGV; # Dump statefile in readable format to terminal and exit (--dump) our $dump = grep {/^--dump$/} @ARGV; # Do not send emails: our $nomail = grep {/^--nomail$/} @ARGV; # E-mail template that is evaled before sending our @template = ; # WARMUP # If statefile is there, eventually load it if ( -e $statefile ) { if ( $auto or $dump) { %state = %{ retrieve($statefile) } } } # Only dump wanted? if ( $dump ) { print Dumper \%state; exit 0 } # Increment tick $state{_tick}++; # BEGIN WOROK # Get volume lists from all servers my @list; for (@servers) { push @list, `vos listvol $_ -format` } chomp for @list; my $current; # name of volume being parsed my %vol; # will contain hash result of parsing my %warn; # list of people and data for > $threshold folks my %nowarn; # same data as in %warn, but for people NOT over threshold # # Copy vol from @list (vos listvol output) into %vol hash. # We read in all values; all values from vol listvol -format # output for a volume are read in. Such as # $vol{user.docelic}{type} = 'RW' # for (@list) { my ($parm, $value) = split(/\s+/, $_, 2); defined $parm and defined $value or next; if ( $parm eq 'name' ) { if ( $value =~ /^$volume_pattern$/ ) { $current = $value } else { $current = '' } } elsif ($current) { $vol{$current}{$parm} = $value } } # # Copy > $threshold people to %warn hash like this: # $warn{docelic}{mail} = 0.9 # while (my($k,$v) = each %vol) { next unless $$v{maxquota}; my ($type,$user) = split(/\./, $k); my $perc = sprintf('%.3f', $$v{diskused} / $$v{maxquota}); if ( $perc > $threshold{$type} ) { $warn{$user}{$type} = $perc; } # A bit of duplication with %warn, but wth.. $nowarn{$user}{$type} = $perc; } # # Reset counter for people who solved their quota thing somehow # my @tmp = keys %state; for my $person ( @tmp ) { next if $person eq '_tick'; unless ( $warn{$person} ) { print "User $person resolved quota problem\n"; delete $state{$person} } } # # Go over %warn, print info to terminal, update %state # when needed to create new entry for new quota detections. # while (my($k,$v) = each %warn) { print "$k "; my @line; while (my($k2,$v2) = each %$v) { push @line, "$k2=$v2"; } print "@line"; # Register person in %state if ( ! $state{$k} ) { $state{$k} = [ $state{_tick}, 2 ]; } print "\n"; } # # See who's entitled to receiving an email at this point; # that is, go over %state, send emails to people who need # to get it in this turn, and exponentially prolong the time # till next message from us. # (They receive message on 1st, 3rd, 7th, 15th, 31st...) # while (my($user,$state) = each %state ) { next if $user eq '_tick'; #print " $$state[0] + $$state[1] == $state{_tick} + 2\n"; if ( $$state[0] + $$state[1] == $state{_tick} + 2 ) { if ($auto and !$nomail) { print "Notifying $user, next notice in $$state[1] days.\n"; email_notify($user); } $$state[1] *= 2; # Following code would re-start the notifications we # send to people after 120 days of not clearing # their issue, but let's keep it disabled for # now.. They get a notice on 127th day, and then # on 255th etc., we don't reset the sequence. #$$state[1] >= 120 and do { # $$state[0] = $state{_tick}; # $$state[1] = 2; #}; } } # Write cache if ( $auto ) { # Already done above, with also printing a nice # message #for ( keys %warn ) { # delete $nowarn{$_} #} #for ( keys %nowarn) { # delete $state{$_}; #} nstore \%state, $statefile; } DONE: exit 0; ############################################## # Helpers below sub email_notify { my $user = shift; my @msg = @template; for ( @msg ) { $_ =~ s/\$vol{(\w+)}{(\w+)}/$vol{"$1.$user"}{$2}/mge; $_ =~ s/\$threshold{(\w+)}/$threshold{$1}/mge; $_ =~ s/\$warn{(\w+)}/$nowarn{$user}{$1}/mge; $_ =~ s/\$CRIT{(\w+)}/$warn{$user}{$1} ? " (APPROACHING QUOTA)" : ""/mge; } #print join('', @msg); open(MAIL, "| mail -s 'Approaching quota limit at HCoop' -a 'From: admins\@hcoop.net' -b 'docelic' '$user\@hcoop.net'"); { no warnings; print main::MAIL join('', @msg); } close MAIL; } # BEGIN_OF_ENTRY # name user.docelic # id 536872079 # serv 69.90.123.67 deleuze.hcoop.net # part /vicepa # status OK # backupID 536872081 # parentID 536872079 # cloneID 536955593 # inUse Y # needsSalvaged N # destroyMe N # type RW # creationDate 1195841197 Fri Nov 23 13:06:37 2007 # accessDate 0 Wed Dec 31 19:00:00 1969 # updateDate 1198364408 Sat Dec 22 18:00:08 2007 # backupDate 1198322931 Sat Dec 22 06:28:51 2007 # copyDate 1195843557 Fri Nov 23 13:45:57 2007 # flags 0 (Optional) # diskused 36718 # maxquota 400000 # minquota 0 (Optional) # filecount 6338 # dayUse 20097 # weekUse 172438 (Optional) # spare2 0 (Optional) # spare3 0 (Optional) # END_OF_ENTRY __DATA__ Hello, Some of your data volumes at HCoop are approaching quota limit. We use quotas to "keep the shop clean", but we regularly grant quota increase requests to all members who submit them. We wouldn't want you hit the quota limit, hence the early notice. This is especially true for the mail and database volumes where the data can accumulate "by itself", and where reaching quota limits may be particularly inconvenient. If you plan to store more data, it might be a good time to submit the quota change request through our portal[1]. Here are your volume statistics: Home: size=$vol{user}{diskused} KB quota=$vol{user}{maxquota} KB threshold=$threshold{user} use=$warn{user} $CRIT{user} Mail: size=$vol{mail}{diskused} KB quota=$vol{mail}{maxquota} KB threshold=$threshold{mail} use=$warn{mail} $CRIT{mail} Databases: size=$vol{db}{diskused} KB quota=$vol{db}{maxquota} KB threshold=$threshold{db} use=$warn{db} $CRIT{db} Please submit your disk quota increase request in a timely manner at: [1] https://members.hcoop.net/portal/quota These automatic notices will be sent to you at exponential intervals; that means in 2 days, 4 days, 8 days, 16 days and so on. The method ensures that, if you retain status-quo (don't submit a quota increase and don't reduce your data size), you get to receive the reminders less and less often. When and if the usage percentage of all your volumes drops below the corresponding quota thresholds, the notifications will stop and reset. Thank you, HCoop