589e4992ae094e56a540795e18f4bf06071019fc
[dbsrgits/DBIx-Class.git] / t / 93storage_replication.t
1 use strict;
2 use warnings;
3 use lib qw(t/lib);
4 use Test::More;
5 use DBICTest;
6
7 BEGIN {
8     eval "use Moose";
9     plan $@
10         ? ( skip_all => 'needs Moose for testing' )
11         : ( tests => 40 );
12 }
13
14 use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
15 use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer';
16 use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer::Random';
17 use_ok 'DBIx::Class::Storage::DBI::Replicated::Replicant';
18 use_ok 'DBIx::Class::Storage::DBI::Replicated';
19
20 ## ----------------------------------------------------------------------------
21 ## Build a class to hold all our required testing data and methods.
22 ## ----------------------------------------------------------------------------
23
24 TESTSCHEMACLASSES: {
25
26     ## --------------------------------------------------------------------- ##
27     ## Create an object to contain your replicated stuff.
28     ## --------------------------------------------------------------------- ##
29     
30     package DBIx::Class::DBI::Replicated::TestReplication;
31    
32     use DBICTest;
33     use base qw/Class::Accessor::Fast/;
34     
35     __PACKAGE__->mk_accessors( qw/schema/ );
36
37     ## Initialize the object
38     
39         sub new {
40             my $class = shift @_;
41             my $self = $class->SUPER::new(@_);
42         
43             $self->schema( $self->init_schema );
44             return $self;
45         }
46     
47     ## Get the Schema and set the replication storage type
48     
49     sub init_schema {
50         my $class = shift @_;
51         my $schema = DBICTest->init_schema(
52             storage_type=>'::DBI::Replicated', 
53             storage_type_args=>{
54                 balancer_type=>'DBIx::Class::Storage::DBI::Replicated::Balancer::Random',
55             });
56
57         return $schema;
58     }
59     
60     sub generate_replicant_connect_info {}
61     sub replicate {}
62     sub cleanup {}
63
64   
65     ## --------------------------------------------------------------------- ##
66     ## Subclass for when you are using SQLite for testing, this provides a fake
67     ## replication support.
68     ## --------------------------------------------------------------------- ##
69         
70     package DBIx::Class::DBI::Replicated::TestReplication::SQLite;
71
72     use DBICTest;
73     use File::Copy;    
74     use base 'DBIx::Class::DBI::Replicated::TestReplication';
75     
76     __PACKAGE__->mk_accessors( qw/master_path slave_paths/ );
77     
78     ## Set the mastep path from DBICTest
79     
80         sub new {
81             my $class = shift @_;
82             my $self = $class->SUPER::new(@_);
83         
84             $self->master_path( DBICTest->_sqlite_dbfilename );
85             $self->slave_paths([
86             "t/var/DBIxClass_slave1.db",
87             "t/var/DBIxClass_slave2.db",    
88         ]);
89         
90             return $self;
91         }    
92         
93     ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
94     ## $storage->connect_info to be used for connecting replicants.
95     
96     sub generate_replicant_connect_info {
97         my $self = shift @_;
98         my @dsn = map {
99             "dbi:SQLite:${_}";
100         } @{$self->slave_paths};
101         
102         return map { [$_,'','',{AutoCommit=>1}] } @dsn;
103     }
104     
105     ## Do a 'good enough' replication by copying the master dbfile over each of
106     ## the slave dbfiles.  If the master is SQLite we do this, otherwise we
107     ## just do a one second pause to let the slaves catch up.
108     
109     sub replicate {
110         my $self = shift @_;
111         foreach my $slave (@{$self->slave_paths}) {
112             copy($self->master_path, $slave);
113         }
114     }
115     
116     ## Cleanup after ourselves.  Unlink all gthe slave paths.
117     
118     sub cleanup {
119         my $self = shift @_;
120         foreach my $slave (@{$self->slave_paths}) {
121             unlink $slave;
122         }     
123     }
124     
125     ## --------------------------------------------------------------------- ##
126     ## Subclass for when you are setting the databases via custom export vars
127     ## This is for when you have a replicating database setup that you are
128     ## going to test against.  You'll need to define the correct $ENV and have
129     ## two slave databases to test against, as well as a replication system
130     ## that will replicate in less than 1 second.
131     ## --------------------------------------------------------------------- ##
132         
133     package DBIx::Class::DBI::Replicated::TestReplication::Custom; 
134     use base 'DBIx::Class::DBI::Replicated::TestReplication';
135     
136     ## Return an Array of ArrayRefs where each ArrayRef is suitable to use for
137     ## $storage->connect_info to be used for connecting replicants.
138     
139     sub generate_replicant_connect_info { 
140         return (
141             [$ENV{"DBICTEST_SLAVE0_DSN"}, $ENV{"DBICTEST_SLAVE0_DBUSER"}, $ENV{"DBICTEST_SLAVE0_DBPASS"}, {AutoCommit => 1}],
142             [$ENV{"DBICTEST_SLAVE1_DSN"}, $ENV{"DBICTEST_SLAVE1_DBUSER"}, $ENV{"DBICTEST_SLAVE1_DBPASS"}, {AutoCommit => 1}],           
143         );
144     }
145     
146     ## pause a bit to let the replication catch up 
147     
148     sub replicate {
149         sleep 1;
150     } 
151 }
152
153 ## ----------------------------------------------------------------------------
154 ## Create an object and run some tests
155 ## ----------------------------------------------------------------------------
156
157 ## Thi first bunch of tests are basic, just make sure all the bits are behaving
158
159 my $replicated_class = DBICTest->has_custom_dsn ?
160     'DBIx::Class::DBI::Replicated::TestReplication::Custom' :
161     'DBIx::Class::DBI::Replicated::TestReplication::SQLite';
162
163 ok my $replicated = $replicated_class->new
164     => 'Created a replication object';
165     
166 isa_ok $replicated->schema
167     => 'DBIx::Class::Schema';
168     
169 isa_ok $replicated->schema->storage
170     => 'DBIx::Class::Storage::DBI::Replicated';
171
172 ok $replicated->schema->storage->meta
173     => 'has a meta object';
174     
175 isa_ok $replicated->schema->storage->master
176     => 'DBIx::Class::Storage::DBI';
177     
178 isa_ok $replicated->schema->storage->pool
179     => 'DBIx::Class::Storage::DBI::Replicated::Pool';
180     
181 isa_ok $replicated->schema->storage->balancer
182     => 'DBIx::Class::Storage::DBI::Replicated::Balancer'; 
183
184 ok my @replicant_connects = $replicated->generate_replicant_connect_info
185     => 'got replication connect information';
186
187 ok my @replicated_storages = $replicated->schema->storage->connect_replicants(@replicant_connects)
188     => 'Created some storages suitable for replicants';
189     
190 isa_ok $replicated->schema->storage->balancer->current_replicant
191     => 'DBIx::Class::Storage::DBI';
192     
193 ok $replicated->schema->storage->pool->has_replicants
194     => 'does have replicants';     
195
196 is $replicated->schema->storage->num_replicants => 2
197     => 'has two replicants';
198        
199 isa_ok $replicated_storages[0]
200     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
201
202 isa_ok $replicated_storages[1]
203     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
204     
205 isa_ok $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}
206     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
207
208 isa_ok $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}
209     => 'DBIx::Class::Storage::DBI::Replicated::Replicant';  
210
211 ## Add some info to the database
212
213 $replicated
214     ->schema
215     ->populate('Artist', [
216         [ qw/artistid name/ ],
217         [ 4, "Ozric Tentacles"],
218     ]);
219                 
220 ## Make sure all the slaves have the table definitions
221
222 $replicated->replicate;
223
224 ## Make sure we can read the data.
225
226 ok my $artist1 = $replicated->schema->resultset('Artist')->find(4)
227     => 'Created Result';
228
229 isa_ok $artist1
230     => 'DBICTest::Artist';
231     
232 is $artist1->name, 'Ozric Tentacles'
233     => 'Found expected name for first result';
234
235 ## Add some new rows that only the master will have  This is because
236 ## we overload any type of write operation so that is must hit the master
237 ## database.
238
239 $replicated
240     ->schema
241     ->populate('Artist', [
242         [ qw/artistid name/ ],
243         [ 5, "Doom's Children"],
244         [ 6, "Dead On Arrival"],
245         [ 7, "Watergate"],
246     ]);
247
248 ## Alright, the database 'cluster' is not in a consistent state.  When we do
249 ## a read now we expect bad news
250
251 is $replicated->schema->resultset('Artist')->find(5), undef
252     => 'read after disconnect fails because it uses a replicant which we have neglected to "replicate" yet';
253
254 ## Make sure all the slaves have the table definitions
255 $replicated->replicate;
256
257 ## Should find some data now
258
259 ok my $artist2 = $replicated->schema->resultset('Artist')->find(5)
260     => 'Sync succeed';
261     
262 isa_ok $artist2
263     => 'DBICTest::Artist';
264     
265 is $artist2->name, "Doom's Children"
266     => 'Found expected name for first result';
267
268 ## What happens when we disconnect all the replicants?
269
270 is $replicated->schema->storage->pool->connected_replicants => 2
271     => "both replicants are connected";
272     
273 $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}->disconnect;
274 $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}->disconnect;
275
276 is $replicated->schema->storage->pool->connected_replicants => 0
277     => "both replicants are now disconnected";
278
279 ## All these should pass, since the database should automatically reconnect
280
281 ok my $artist3 = $replicated->schema->resultset('Artist')->find(6)
282     => 'Still finding stuff.';
283     
284 isa_ok $artist3
285     => 'DBICTest::Artist';
286     
287 is $artist3->name, "Dead On Arrival"
288     => 'Found expected name for first result';
289
290 is $replicated->schema->storage->pool->connected_replicants => 1
291     => "One replicant reconnected to handle the job";
292     
293 ## What happens when we try to select something that doesn't exist?
294
295 ok ! $replicated->schema->resultset('Artist')->find(666)
296     => 'Correctly failed to find something.';
297     
298 ## test the reliable option
299
300 TESTRELIABLE: {
301         
302         $replicated->schema->storage->set_reliable_storage;
303         
304         ok $replicated->schema->resultset('Artist')->find(2)
305             => 'Read from master 1';
306         
307         ok $replicated->schema->resultset('Artist')->find(5)
308             => 'Read from master 2';
309             
310     $replicated->schema->storage->set_balanced_storage;     
311             
312         ok $replicated->schema->resultset('Artist')->find(3)
313         => 'Read from replicant';
314 }
315
316 ## Make sure when reliable goes out of scope, we are using replicants again
317
318 ok $replicated->schema->resultset('Artist')->find(1)
319     => 'back to replicant 1.';
320     
321 ok $replicated->schema->resultset('Artist')->find(2)
322     => 'back to replicant 2.';
323     
324
325 ## Delete the old database files
326 $replicated->cleanup;
327
328
329
330
331
332