Merge 'trunk' into 'replication_dedux'
[dbsrgits/DBIx-Class.git] / t / 93storage_replication.t
CommitLineData
e4dc89b3 1use strict;
2use warnings;
3use lib qw(t/lib);
e4dc89b3 4use Test::More;
857d66ca 5use DBICTest;
8f7986d6 6
86583fa7 7BEGIN {
2bf79155 8 eval "use Moose";
86583fa7 9 plan $@
2bf79155 10 ? ( skip_all => 'needs Moose for testing' )
cb6ec758 11 : ( tests => 40 );
26ab719a 12}
13
14use_ok 'DBIx::Class::Storage::DBI::Replicated::Pool';
15use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer';
cb6ec758 16use_ok 'DBIx::Class::Storage::DBI::Replicated::Balancer::Random';
26ab719a 17use_ok 'DBIx::Class::Storage::DBI::Replicated::Replicant';
18use_ok 'DBIx::Class::Storage::DBI::Replicated';
0f83441a 19
20## ----------------------------------------------------------------------------
21## Build a class to hold all our required testing data and methods.
22## ----------------------------------------------------------------------------
23
857d66ca 24TESTSCHEMACLASSES: {
2bf79155 25
857d66ca 26 ## --------------------------------------------------------------------- ##
27 ## Create an object to contain your replicated stuff.
28 ## --------------------------------------------------------------------- ##
29
2bf79155 30 package DBIx::Class::DBI::Replicated::TestReplication;
31
32 use DBICTest;
33 use base qw/Class::Accessor::Fast/;
34
857d66ca 35 __PACKAGE__->mk_accessors( qw/schema/ );
2bf79155 36
37 ## Initialize the object
38
39 sub new {
26ab719a 40 my $class = shift @_;
41 my $self = $class->SUPER::new(@_);
2bf79155 42
43 $self->schema( $self->init_schema );
2bf79155 44 return $self;
45 }
46
26ab719a 47 ## Get the Schema and set the replication storage type
2bf79155 48
49 sub init_schema {
50 my $class = shift @_;
cb6ec758 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
2bf79155 57 return $schema;
58 }
26ab719a 59
857d66ca 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
26ab719a 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 {
857d66ca 97 my $self = shift @_;
26ab719a 98 my @dsn = map {
99 "dbi:SQLite:${_}";
100 } @{$self->slave_paths};
101
857d66ca 102 return map { [$_,'','',{AutoCommit=>1}] } @dsn;
26ab719a 103 }
104
105 ## Do a 'good enough' replication by copying the master dbfile over each of
50336325 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.
26ab719a 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 }
857d66ca 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 }
2bf79155 151}
152
153## ----------------------------------------------------------------------------
154## Create an object and run some tests
155## ----------------------------------------------------------------------------
156
26ab719a 157## Thi first bunch of tests are basic, just make sure all the bits are behaving
2bf79155 158
857d66ca 159my $replicated_class = DBICTest->has_custom_dsn ?
160 'DBIx::Class::DBI::Replicated::TestReplication::Custom' :
161 'DBIx::Class::DBI::Replicated::TestReplication::SQLite';
162
163ok my $replicated = $replicated_class->new
164 => 'Created a replication object';
2bf79155 165
26ab719a 166isa_ok $replicated->schema
2bf79155 167 => 'DBIx::Class::Schema';
168
26ab719a 169isa_ok $replicated->schema->storage
170 => 'DBIx::Class::Storage::DBI::Replicated';
171
172ok $replicated->schema->storage->meta
173 => 'has a meta object';
174
175isa_ok $replicated->schema->storage->master
176 => 'DBIx::Class::Storage::DBI';
177
178isa_ok $replicated->schema->storage->pool
179 => 'DBIx::Class::Storage::DBI::Replicated::Pool';
180
181isa_ok $replicated->schema->storage->balancer
182 => 'DBIx::Class::Storage::DBI::Replicated::Balancer';
183
184ok my @replicant_connects = $replicated->generate_replicant_connect_info
185 => 'got replication connect information';
186
955a6df6 187ok my @replicated_storages = $replicated->schema->storage->connect_replicants(@replicant_connects)
26ab719a 188 => 'Created some storages suitable for replicants';
189
cb6ec758 190isa_ok $replicated->schema->storage->balancer->current_replicant
26ab719a 191 => 'DBIx::Class::Storage::DBI';
192
193ok $replicated->schema->storage->pool->has_replicants
194 => 'does have replicants';
195
196is $replicated->schema->storage->num_replicants => 2
197 => 'has two replicants';
198
199isa_ok $replicated_storages[0]
200 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
201
202isa_ok $replicated_storages[1]
203 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
204
205isa_ok $replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}
206 => 'DBIx::Class::Storage::DBI::Replicated::Replicant';
207
208isa_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
226ok my $artist1 = $replicated->schema->resultset('Artist')->find(4)
227 => 'Created Result';
228
229isa_ok $artist1
230 => 'DBICTest::Artist';
231
232is $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
251is $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
259ok my $artist2 = $replicated->schema->resultset('Artist')->find(5)
260 => 'Sync succeed';
261
262isa_ok $artist2
263 => 'DBICTest::Artist';
264
265is $artist2->name, "Doom's Children"
266 => 'Found expected name for first result';
267
268## What happens when we disconnect all the replicants?
269
50336325 270is $replicated->schema->storage->pool->connected_replicants => 2
271 => "both replicants are connected";
272
26ab719a 273$replicated->schema->storage->replicants->{"t/var/DBIxClass_slave1.db"}->disconnect;
274$replicated->schema->storage->replicants->{"t/var/DBIxClass_slave2.db"}->disconnect;
275
50336325 276is $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
26ab719a 281ok my $artist3 = $replicated->schema->resultset('Artist')->find(6)
282 => 'Still finding stuff.';
2bf79155 283
26ab719a 284isa_ok $artist3
285 => 'DBICTest::Artist';
2bf79155 286
26ab719a 287is $artist3->name, "Dead On Arrival"
288 => 'Found expected name for first result';
2bf79155 289
50336325 290is $replicated->schema->storage->pool->connected_replicants => 1
291 => "One replicant reconnected to handle the job";
6f6fb437 292
293## What happens when we try to select something that doesn't exist?
294
295ok ! $replicated->schema->resultset('Artist')->find(666)
296 => 'Correctly failed to find something.';
cb6ec758 297
298## test the reliable option
299
300TESTRELIABLE: {
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)
9c748388 313 => 'Read from replicant';
cb6ec758 314}
315
9c748388 316## Make sure when reliable goes out of scope, we are using replicants again
cb6ec758 317
318ok $replicated->schema->resultset('Artist')->find(1)
319 => 'back to replicant 1.';
320
321ok $replicated->schema->resultset('Artist')->find(2)
322 => 'back to replicant 2.';
323
2156bbdd 324
0f83441a 325## Delete the old database files
50336325 326$replicated->cleanup;
0f83441a 327
328
329
330
331
332