File Coverage

lib/WebService/Amazon/Route53/Caching.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3            
4             WebService::Amazon::Route53::Caching - Caching layer for the Amazon Route 53 API
5            
6             =head1 SYNOPSIS
7            
8             WebService::Amazon::Route53::Caching provides an caching layer on top of
9             the existing L<WebService::Amazon::Route53> module, which presents an interface
10             to the Amazon Route 53 DNS service.
11            
12             =cut
13              
14             =head1 DESCRIPTION
15            
16             This module overrides the base behaviour of the L<WebService::Amazon::Route53>
17             object to provide two specific speedups:
18            
19             =over 8
20            
21             =item We force the use of HTTP Keep-Alive when accessing the remote Amazon API end-point.
22            
23             =item We cache the mapping between zone-names and Amazon IDs
24            
25             =back
26            
27             The reason for the existance of this module was observed performance
28             issues with the native client. A user of the Route53 API wishes to use
29             the various object methods against B<zones>, but the Amazon API requires
30             that you use their internal IDs.
31            
32             For example rather than working with a zone such as "steve.org.uk", you
33             must pass in a zone_id of the form "123ZONEID". Discovering the ID
34             of a zone is possible via L<get_hosted_zone|WebService::Amazon::Route53/"get_hosted_zone"> method.
35            
36             Unfortunately the implementation of the B<get_hosted_zone> method essentially
37             boils down to fetching all possible zones, and then performing a string
38             comparison on their names.
39            
40             This module was born to cache the ID-data of individual zones, allowing
41             significant speedups when dealing with a number of zones.
42            
43             =cut
44              
45             =head1 CACHING
46            
47             This module supports two different types of caching:
48            
49             =over 8
50            
51             =item Caching via the fast in-memory datastore, Redis.
52            
53             =item Caching via the L<DB_File> module.
54            
55             =back
56            
57             To specify the method you need to pass the appropriate argument
58             to the constructor of this class.
59            
60             The simplest approach involves passing a filename to use as the
61             DB-store:
62            
63             =for example begin
64            
65             my $c = WebService::Amazon::Route53::Caching->new( key => "xx",
66             id => "xx",
67             path => "/tmp/x.db" );
68            
69             $c->....
70            
71             =for example end
72            
73             The following example uses Redis :
74            
75             =for example begin
76            
77             my $r = new Redis;
78            
79             my $c = WebService::Amazon::Route53::Caching->new( key => "xx",
80             id => "xx",
81             redis => $r );
82            
83             $c->....
84            
85             =for example end
86            
87            
88             All other class methods remain identical to those implemented in the
89             parent.
90            
91             =cut
92              
93             =head1 COPYRIGHT AND LICENSE
94            
95             Copyright (C) 2014 Steve Kemp <steve@steve.org.uk>.
96            
97             This library is free software. You can modify and or distribute it under
98             the same terms as Perl itself.
99            
100             =cut
101              
102              
103             package WebService::Amazon::Route53::Caching;
104              
105 1     1   40637 use strict;
  1         2  
  1         95  
106 1     1   5 use warnings;
  1         3  
  1         39  
107              
108              
109 1     1   6 use base ("WebService::Amazon::Route53");
  1         7  
  1         896  
110 1     1   7 use Carp;
  1         2  
  1         105  
111              
112 1     1   683 use WebService::Amazon::Route53::Caching::Store::DBM;
  0            
  0            
113             use WebService::Amazon::Route53::Caching::Store::NOP;
114             use WebService::Amazon::Route53::Caching::Store::Redis;
115              
116             use JSON;
117             our $VERSION = "0.4.1";
118              
119              
120              
121             =begin doc
122            
123             Override the constructor to enable Keep-Alive in the UserAgent
124             object. This cuts down request time by 50%.
125            
126             =end doc
127            
128             =cut
129              
130             sub new
131             {
132                 my ( $class, %args ) = (@_);
133              
134             # Invoke the superclass.
135                 my $self = $class->SUPER::new(%args);
136              
137              
138             #
139             # This is messy only because the length of the classes.
140             #
141             # We always create a caching mechanism, here we determine the correct
142             # one to load.
143             #
144             # If none is explicitly specified then we use the NOP one.
145             #
146                 if ( $args{ 'path' } )
147                 {
148                     $self->{ '_cache' } =
149                       WebService::Amazon::Route53::Caching::Store::DBM->new(
150                                                                   path => $args{ 'path' } );
151                 }
152                 elsif ( $args{ 'redis' } )
153                 {
154                     $self->{ '_cache' } =
155                       WebService::Amazon::Route53::Caching::Store::Redis->new(
156                                                                 redis => $args{ 'redis' } );
157                 }
158                 else
159                 {
160                     $self->{ '_cache' } =
161                       WebService::Amazon::Route53::Caching::Store::NOP->new();
162                 }
163              
164             # Update the User-Agent to use Keep-Alive.
165                 $self->{ 'ua' } = LWP::UserAgent->new( keep_alive => 10 );
166              
167                 return $self;
168             }
169              
170              
171              
172             =begin doc
173            
174             Find data about the hosted zone, preferring our local cache first.
175            
176             =end doc
177            
178             =cut
179              
180             sub find_hosted_zone
181             {
182                 my ( $self, %args ) = (@_);
183              
184                 if ( !defined $args{ 'name' } )
185                 {
186                     carp "Required parameter 'name' is not defined";
187                 }
188              
189             #
190             # Lookup from the cache - deserializing after the fetch.
191             #
192                 my $data = $self->{ '_cache' }->get( "zone_data_" . $args{ 'name' } );
193                 if ( $data && length($data) )
194                 {
195                     my $obj = from_json($data);
196                     return ($obj);
197                 }
198              
199             #
200             # OK that failed, so revert to using our superclass.
201             #
202                 my $result = $self->SUPER::find_hosted_zone(%args);
203              
204             #
205             # Store the result in our cache so that the next time we'll get a hit.
206             #
207                 if ( $result )
208                 {
209             # Store the values of the lookup.
210                     $self->{ '_cache' }
211                       ->set( "zone_data_" . $args{ 'name' }, to_json($result) );
212              
213             # Store the mapping from the returned ID to the domain-data.
214                     my $id = $result->{'id'};
215                     if ( $id )
216                     {
217                         $self->{ '_cache' }
218                           ->set( "zone_data_id_" . $id, $args{'name'} );
219                     }
220                 }
221              
222                 return ($result);
223             }
224              
225              
226              
227             =begin doc
228            
229             When a zone is created the Amazon ID is returned, so we can pre-emptively
230             cache that.
231            
232             =end doc
233            
234             =cut
235              
236             sub create_hosted_zone
237             {
238                 my ( $self, %args ) = @_;
239              
240                 my $result = $self->SUPER::create_hosted_zone(%args);
241              
242             #
243             # Update the cache.
244             #
245                 if ( $result && $result->{ 'zone' } )
246                 {
247                     $self->{ '_cache' }
248                       ->set( "zone_data_" . $args{ 'name' }, to_json($result) );
249              
250             #
251             # Store the mapping from the returned ID to the domain-data.
252             #
253                     my $id = $result->{'zone'}->{'id'} || $result->{'id'};
254                     if ( $id )
255                     {
256                         $id =~ s/\/hostedzone\///g;
257                         $self->{ '_cache' }
258                           ->set( "zone_data_id_" . $id, $args{'name'} );
259                     }
260              
261                 }
262              
263                 return ($result);
264             }
265              
266              
267              
268             =begin doc
269            
270             When a zone is deleted we'll remove the association we have between
271             the name and the ID.
272            
273             =end doc
274            
275             =cut
276              
277             sub delete_hosted_zone
278             {
279                 my ( $self, %args ) = (@_);
280              
281                 if ( !defined $args{ 'zone_id' } )
282                 {
283                     carp "Required parameter 'zone_id' is not defined";
284                 }
285              
286              
287             #
288             # Remove the cache-data associated with this key.
289             #
290             # First lookup the name of the zone, so we can find the keyu
291             # which is based on the zone-name.
292             #
293                 my $zone = $self->{ '_cache' }->get( "zone_data_id_" . $args{'zone_id'} );
294              
295                 $self->{ '_cache' }->del( "zone_data_" . $zone ) if ( $zone );
296                 $self->{ '_cache' }->del( "zone_data_id_" . $args{ 'zone_id' } );
297              
298                 return ( $self->SUPER::delete_hosted_zone(%args) );
299             }
300              
301              
302             =begin doc
303            
304             Allow the caller to gain access to our caching object.
305            
306             =end doc
307            
308             =cut
309              
310             sub cache
311             {
312                 my( $self ) = ( @_ );
313                 return( $self->{'_cache'} );
314             }
315              
316             1;
317              
318