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