--- Revision None +++ Revision 316663353936 @@ -0,0 +1,96 @@ +#!/usr/bin/perl + +# Takes one argument which is the URL of the JSON document to use +# The JSON document describes the various +# replications to ensure are running and what indexes need rebuilding and +# how often that should be done + +use JSON::Any; +use LWP::UserAgent; +use Storable; +use CouchDB::Client; +use Time::ParseDate qw(parsedate); # from libtime-modules-perl + +my ($url) = @ARGV; +die "First and only argument must be a URL" unless $url; + +# Get our persistent state (or a blank one) +my $state = {}; +eval { + # We can keep it in the temp folder because it doesn't matter much if we loose it, we just count from scratch + $state = retrieve("/tmp/couch_maint.state"); +}; + +# Since we've been passed a URL use LWP to grab it (rather than CouchDB::Client) +my $ua = LWP::UserAgent->new(); +my $response = $ua->get($url); +die "Failed to download document" unless $response->is_success(); +my $info = JSON::Any->jsonToObj($response->content); + +die "Document doesn't contain a 'url' key so don't know how to contact the couch service\n" unless $info->{uri}; +my $couch_client = CouchDB::Client->new( uri => $info->{uri} ); +$couch_client->testConnection or die "Can't talk to the couch database on $info->{uri}"; + +die "No databases field in the document" unless $info->{databases}; +# For each database +while(my ($database, $db_info) = each %{$info->{databases}}) { + my $db = $couch_client->newDB($database); + # Trigger the indexes + if ($db_info->{index}) { + while( my ($index, $index_info) = each %{$db_info->{index}}) { + if (run_now($database,$index)) { + my $design_doc = $db->newDesignDoc("_design/$index"); + $design_doc->retrieve(); + my @views = $design_doc->listViews(); + # Eval the view query because it might timeout if the view is a long way behind. + # This is ok, at least we triggered it and by evaling we still get to trigger the + # other views (admittedly significantly slower than we might like but better than nothing) + # The only real danger is that each time we trigger but timeout we eat a handle off the + # couch server which will only be returned when the view eventually finishes building and + # hence we can run it out of handles. Not a lot we can do about that here though really. + eval { + # In theory triggering the first view + # will update the whole lot but lets + # just trigger all of them to be safe + for(@views) { + $design_doc->queryView($_, limit => 0); + } + }; + warn $@ if $@; + calc_next($index_info,$database,$index); + } + } + } + # Set the replication, we only do push replication (you can do this over and over again and couch is ok with that) + if ($db_info->{replicate}) { + foreach my $target (@{$db_info->{replicate}}) { + $db->replicate(target => $target, continuous => 1); + } + } +} +store($state,"/tmp/couch_maint.state"); + +sub run_now { + my ($a,$b) = @_; + return 1 unless defined($state->{$a}->{$b}); # If we know nothing we want to run it now + return time()>$state->{$a}->{$b}->{next_run}; +} + +sub calc_next { + my ($info,$a,$b) = @_; + my ($next_time,$error) = parsedate($info->{every}); + if ($next_time){ + # We subtract 10 seconds from the next run time so that + # under normal running (when we can index very quickly) + # doing it every minute from cron might actually cause + # it to happen every minute. Without this if we are + # invoked once a minute there is a chance we will decide + # not to index because it was only 59 seconds since we last + # did it and therefore we won't reindex until almost + # 2 minutes have passed. + $state->{$a}->{$b}->{next_run} = $next_time-10; + } + else { + die "Error parsing time reference '$info->{every}': $error\n"; + } +}