Revision 316663353936 () - Diff

Link to this snippet: https://friendpaste.com/4lSkgZR0xdAJ2Od42U0Yiz
Embed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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";
}
}