Initial version of import list script.
[hcoop/scripts.git] / quotacheck
1 #!/usr/bin/perl
2
3 # docelic, Sat Dec 22 18:35:37 EST 2007
4
5 # Script warns people when they're approaching quota. The purpose
6 # is not to 'behave' users but to give them a notice they should
7 # fill quota increase request. (We want to prevent people from
8 # ever actually running into quota limits, especially for Mail
9 # and Database volumes. We use quotas to keep the shop clean, not
10 # to impose restrictions).
11
12 # Script retrieves volume info from deleuze, from there it
13 # reads mail., user. and db. volumes into %vol, then
14 # sees which volumes have > $threshold usage, relative to volume
15 # quota.
16 #
17 # For volumes above threshold, it extracts the owner name from
18 # volume name (like, mail.USERNAME) and saves info to %warn.
19 #
20 # vos listvol is parsed into structure like this:
21 #
22 # $vol{user.docelic}{diskused} = 36718 ( All fields from the
23 # output of vos listvol are loaded into the hash. list of
24 # fields is at the bottom of this script )
25 #
26 # people who are over quota are in %warn:
27 #
28 # $warn{docelic}{user} = 0.9
29 # $warn{docelic}{mail} = 0.74
30 #
31 # "Offenders" are saved to $state{$user} = [ run_number, exp_inc ]
32 # and removed when they resolve their quota situation. For this
33 # to work (and emails to get sent), pass --auto (most certain when
34 # running from cron).
35 #
36 # run_number is the run (tick) when they were first spotted to
37 # be over safe value, and exp_inc is the interval at which they
38 # need to be reminded. (We use 1, 2, 4, 8, 16...). Each 'tick'
39 # and number usually refers to 1 day (if ran from cron daily)
40
41 # In the current implimentation, folks will be reminded on
42 # 1st, 3rd, 7th, 15th and 31st day after their quota overrun.
43 # Then they will be given 2 months of no messages, and then
44 # after 3 months all together, the "warning cycle" will start again.
45
46 # - If user is over 0.96 on mail or db volume, we will add 2% to his
47 # quota one-time to alleviate immediate problem.
48
49 use warnings;
50 use strict;
51 use Storable qw/nstore retrieve/;
52 use Fatal qw/open write read close/;
53 use Data::Dumper qw/Dumper/;
54
55 # Warn threshold
56 our %threshold = ( user => 0.8, mail => 0.6, db => 0.4 );
57 # AFS servers with volumes
58 our @servers = qw/deleuze/;
59 # Which volumes we want?
60 # (Your volumes need to be in format (TYPE).USERNAME, otherwise
61 # you will have to modify below where we take .USERNAME to mean
62 # person's USERNAME). Changing this will also trigger changes
63 # in template email (see DATA below).
64 our $volume_pattern = '(mail|user|db)\.[a-zA-Z0-9]+'; # implicit ^...$
65 # Cache file
66 our $statefile = '/var/cache/hcoop/quotacheck';
67 # Hash structure containing load of $statefile
68 our %state;
69 # Update cache & send out emails (--auto), or just run terminal report
70 our $auto = grep {/^--auto$/} @ARGV;
71 # Dump statefile in readable format to terminal and exit (--dump)
72 our $dump = grep {/^--dump$/} @ARGV;
73 # Do not send emails:
74 our $nomail = grep {/^--nomail$/} @ARGV;
75 # E-mail template that is evaled before sending
76 our @template = <DATA>;
77
78
79 # WARMUP
80
81 # If statefile is there, eventually load it
82 if ( -e $statefile ) {
83 if ( $auto or $dump) { %state = %{ retrieve($statefile) } }
84 }
85
86 # Only dump wanted?
87 if ( $dump ) { print Dumper \%state; exit 0 }
88
89 # Increment tick
90 $state{_tick}++;
91
92
93 # BEGIN WOROK
94
95 # Get volume lists from all servers
96 my @list;
97 for (@servers) { push @list, `vos listvol $_ -format` }
98 chomp for @list;
99
100 my $current; # name of volume being parsed
101 my %vol; # will contain hash result of parsing
102 my %warn; # list of people and data for > $threshold folks
103 my %nowarn; # same data as in %warn, but for people NOT over threshold
104
105 #
106 # Copy vol from @list (vos listvol output) into %vol hash.
107 # We read in all values; all values from vol listvol -format
108 # output for a volume are read in. Such as
109 # $vol{user.docelic}{type} = 'RW'
110 #
111 for (@list) {
112 my ($parm, $value) = split(/\s+/, $_, 2);
113 defined $parm and defined $value or next;
114
115 if ( $parm eq 'name' ) {
116 if ( $value =~ /^$volume_pattern$/ ) {
117 $current = $value
118 } else {
119 $current = ''
120 }
121 } elsif ($current) {
122 $vol{$current}{$parm} = $value
123 }
124 }
125
126 #
127 # Copy > $threshold people to %warn hash like this:
128 # $warn{docelic}{mail} = 0.9
129 #
130 while (my($k,$v) = each %vol) {
131 next unless $$v{maxquota};
132 my ($type,$user) = split(/\./, $k);
133
134 my $perc = sprintf('%.3f', $$v{diskused} / $$v{maxquota});
135
136 if ( $perc > $threshold{$type} ) {
137 $warn{$user}{$type} = $perc;
138 }
139
140 # A bit of duplication with %warn, but wth..
141 $nowarn{$user}{$type} = $perc;
142 }
143
144 #
145 # Reset counter for people who solved their quota thing somehow
146 #
147 my @tmp = keys %state;
148 for my $person ( @tmp ) {
149 next if $person eq '_tick';
150 unless ( $warn{$person} ) {
151 print "User $person resolved quota problem\n";
152 delete $state{$person}
153 }
154 }
155
156 #
157 # Go over %warn, print info to terminal, update %state
158 # when needed to create new entry for new quota detections.
159 #
160 while (my($k,$v) = each %warn) {
161 print "$k ";
162 my @line;
163 while (my($k2,$v2) = each %$v) {
164 push @line, "$k2=$v2";
165 }
166 print "@line";
167
168 # Register person in %state
169 if ( ! $state{$k} ) {
170 $state{$k} = [ $state{_tick}, 2 ];
171 }
172
173 print "\n";
174 }
175
176 #
177 # See who's entitled to receiving an email at this point;
178 # that is, go over %state, send emails to people who need
179 # to get it in this turn, and exponentially prolong the time
180 # till next message from us.
181 # (They receive message on 1st, 3rd, 7th, 15th, 31st...)
182 #
183 while (my($user,$state) = each %state ) {
184 next if $user eq '_tick';
185 #print " $$state[0] + $$state[1] == $state{_tick} + 2\n";
186
187 if ( $$state[0] + $$state[1] == $state{_tick} + 2 ) {
188
189 if ($auto and !$nomail) {
190 print "Notifying $user, next notice in $$state[1] days.\n";
191 email_notify($user);
192 }
193
194 $$state[1] *= 2;
195
196 # Following code would re-start the notifications we
197 # send to people after 120 days of not clearing
198 # their issue, but let's keep it disabled for
199 # now.. They get a notice on 127th day, and then
200 # on 255th etc., we don't reset the sequence.
201
202 #$$state[1] >= 120 and do {
203 # $$state[0] = $state{_tick};
204 # $$state[1] = 2;
205 #};
206 }
207 }
208
209
210 # Write cache
211 if ( $auto ) {
212 # Already done above, with also printing a nice
213 # message
214 #for ( keys %warn ) {
215 # delete $nowarn{$_}
216 #}
217 #for ( keys %nowarn) {
218 # delete $state{$_};
219 #}
220
221 nstore \%state, $statefile;
222 }
223
224 DONE:
225 exit 0;
226 ##############################################
227 # Helpers below
228
229
230 sub email_notify {
231 my $user = shift;
232
233 my @msg = @template;
234 for ( @msg ) {
235 $_ =~ s/\$vol{(\w+)}{(\w+)}/$vol{"$1.$user"}{$2}/mge;
236 $_ =~ s/\$threshold{(\w+)}/$threshold{$1}/mge;
237 $_ =~ s/\$warn{(\w+)}/$nowarn{$user}{$1}/mge;
238 $_ =~ s/\$CRIT{(\w+)}/$warn{$user}{$1} ? " (APPROACHING QUOTA)" : ""/mge;
239 }
240 #print join('', @msg);
241 open(MAIL, "| mail -s 'Approaching quota limit at HCoop' -a 'From: admins\@hcoop.net' -b 'docelic' '$user\@hcoop.net'");
242 { no warnings; print main::MAIL join('', @msg); }
243 close MAIL;
244 }
245
246
247 # BEGIN_OF_ENTRY
248 # name user.docelic
249 # id 536872079
250 # serv 69.90.123.67 deleuze.hcoop.net
251 # part /vicepa
252 # status OK
253 # backupID 536872081
254 # parentID 536872079
255 # cloneID 536955593
256 # inUse Y
257 # needsSalvaged N
258 # destroyMe N
259 # type RW
260 # creationDate 1195841197 Fri Nov 23 13:06:37 2007
261 # accessDate 0 Wed Dec 31 19:00:00 1969
262 # updateDate 1198364408 Sat Dec 22 18:00:08 2007
263 # backupDate 1198322931 Sat Dec 22 06:28:51 2007
264 # copyDate 1195843557 Fri Nov 23 13:45:57 2007
265 # flags 0 (Optional)
266 # diskused 36718
267 # maxquota 400000
268 # minquota 0 (Optional)
269 # filecount 6338
270 # dayUse 20097
271 # weekUse 172438 (Optional)
272 # spare2 0 (Optional)
273 # spare3 0 (Optional)
274 # END_OF_ENTRY
275
276 __DATA__
277 Hello,
278
279 Some of your data volumes at HCoop are approaching quota
280 limit.
281
282 We use quotas to "keep the shop clean", but we regularly
283 grant quota increase requests to all members who submit them.
284
285 We wouldn't want you hit the quota limit, hence the early
286 notice. This is especially true for the mail and database
287 volumes where the data can accumulate "by itself", and where
288 reaching quota limits may be particularly inconvenient.
289 If you plan to store more data, it might be a good
290 time to submit the quota change request through our portal[1].
291
292 Here are your volume statistics:
293
294 Home:
295 size=$vol{user}{diskused} KB
296 quota=$vol{user}{maxquota} KB
297 threshold=$threshold{user}
298 use=$warn{user} $CRIT{user}
299
300 Mail:
301 size=$vol{mail}{diskused} KB
302 quota=$vol{mail}{maxquota} KB
303 threshold=$threshold{mail}
304 use=$warn{mail} $CRIT{mail}
305
306 Databases:
307 size=$vol{db}{diskused} KB
308 quota=$vol{db}{maxquota} KB
309 threshold=$threshold{db}
310 use=$warn{db} $CRIT{db}
311
312 Please submit your disk quota increase request in a timely
313 manner at:
314
315 [1] https://members.hcoop.net/portal/quota
316
317
318 These automatic notices will be sent to you at exponential
319 intervals; that means in 2 days, 4 days, 8 days, 16 days and
320 so on. The method ensures that, if you retain status-quo
321 (don't submit a quota increase and don't reduce your data size),
322 you get to receive the reminders less and less often.
323 When and if the usage percentage of all your volumes drops below
324 the corresponding quota thresholds, the notifications will stop
325 and reset.
326
327 Thank you,
328 HCoop
329