ar-octopus 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.mkdn +49 -0
- data/Rakefile +106 -0
- data/VERSION +1 -0
- data/doc/api.textile +98 -0
- data/doc/features.textile +74 -0
- data/doc/libraries.textile +70 -0
- data/doc/shards.yml +76 -0
- data/lib/octopus.rb +30 -0
- data/lib/octopus/controller.rb +2 -0
- data/lib/octopus/migration.rb +41 -0
- data/lib/octopus/model.rb +57 -0
- data/lib/octopus/proxy.rb +195 -0
- data/spec/config/shards.yml +76 -0
- data/spec/database_connection.rb +6 -0
- data/spec/database_models.rb +23 -0
- data/spec/migrations/1_create_users_on_master.rb +9 -0
- data/spec/migrations/2_create_users_on_canada.rb +11 -0
- data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
- data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
- data/spec/octopus/controller_spec.rb +7 -0
- data/spec/octopus/migration_spec.rb +79 -0
- data/spec/octopus/model_spec.rb +122 -0
- data/spec/octopus/octopus_spec.rb +15 -0
- data/spec/octopus/proxy_spec.rb +112 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +27 -0
- metadata +123 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Octopus::Migration do
|
4
|
+
it "should run just in the master shard" do
|
5
|
+
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 1)
|
6
|
+
|
7
|
+
User.using(:master).find_by_name("Master").should_not be_nil
|
8
|
+
User.using(:canada).find_by_name("Master").should be_nil
|
9
|
+
|
10
|
+
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 1)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should run on specific shard" do
|
14
|
+
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 2)
|
15
|
+
|
16
|
+
User.using(:master).find_by_name("Sharding").should be_nil
|
17
|
+
User.using(:canada).find_by_name("Sharding").should_not be_nil
|
18
|
+
|
19
|
+
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 2)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should run on specifieds shards" do
|
23
|
+
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 3)
|
24
|
+
|
25
|
+
User.using(:brazil).find_by_name("Both").should_not be_nil
|
26
|
+
User.using(:canada).find_by_name("Both").should_not be_nil
|
27
|
+
|
28
|
+
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 3)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should run on specified group" do
|
32
|
+
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 4)
|
33
|
+
|
34
|
+
User.using(:canada).find_by_name("Group").should_not be_nil
|
35
|
+
User.using(:brazil).find_by_name("Group").should_not be_nil
|
36
|
+
User.using(:russia).find_by_name("Group").should_not be_nil
|
37
|
+
|
38
|
+
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 4)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should run on multiples groups" do
|
42
|
+
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 5)
|
43
|
+
|
44
|
+
User.using(:canada).where(:name => "MultipleGroup").all.size.should == 2
|
45
|
+
User.using(:brazil).where(:name => "MultipleGroup").all.size.should == 2
|
46
|
+
User.using(:russia).where(:name => "MultipleGroup").all.size.should == 2
|
47
|
+
|
48
|
+
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 5)
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "should raise a exception when" do
|
52
|
+
it "you specify a invalid shard name" do
|
53
|
+
lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 6) }.should raise_error("Nonexistent Shard Name: amazing_shard")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "you specify a invalid shard name, even if you have multiple shards, and one of them are right" do
|
57
|
+
lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 7) }.should raise_error("Nonexistent Shard Name: invalid_shard")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "you specify a invalid group name" do
|
61
|
+
lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 8) }.should raise_error("Nonexistent Group Name: invalid_group")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "you specify a invalid group name, even if you have multiple groups, and one of them are right" do
|
65
|
+
lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 9) }.should raise_error("Nonexistent Group Name: invalid_group")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "when using replication" do
|
70
|
+
it "should run writes on master when you use replication" do
|
71
|
+
pending()
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should run in all shards, master or another shards" do
|
75
|
+
pending()
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Octopus::Model do
|
4
|
+
describe "#using method" do
|
5
|
+
it "should return self after calling the #using method" do
|
6
|
+
User.using(:canada).should == User
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should allow selecting the shards on scope" do
|
10
|
+
User.using(:canada).create!(:name => 'oi')
|
11
|
+
User.using(:canada).count.should == 1
|
12
|
+
User.count.should == 0
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should allow scoping dynamically" do
|
16
|
+
User.using(:canada).using(:master).using(:canada).create!(:name => 'oi')
|
17
|
+
User.using(:canada).using(:master).count.should == 0
|
18
|
+
User.using(:master).using(:canada).count.should == 1
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should clean the current_shard after executing the current query" do
|
22
|
+
User.using(:canada).create!(:name => "oi")
|
23
|
+
User.count.should == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should support both groups and alone shards" do
|
27
|
+
User.using(:alone_shard).create!(:name => "Alone")
|
28
|
+
User.using(:alone_shard).count.should == 1
|
29
|
+
User.using(:canada).count.should == 0
|
30
|
+
User.using(:brazil).count.should == 0
|
31
|
+
User.count.should == 0
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "passing a block" do
|
35
|
+
it "should allow queries be executed inside the block, ponting to a specific shard" do
|
36
|
+
User.using(:canada) do
|
37
|
+
User.create(:name => "oi")
|
38
|
+
end
|
39
|
+
|
40
|
+
User.using(:canada).count.should == 1
|
41
|
+
User.using(:master).count.should == 0
|
42
|
+
User.count.should == 0
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should allow execute queries inside a model" do
|
46
|
+
u = User.new
|
47
|
+
u.awesome_queries()
|
48
|
+
User.using(:canada).count.should == 1
|
49
|
+
User.count.should == 0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "when you have a relationship" do
|
54
|
+
it "should find all models in the specified shard" do
|
55
|
+
pending()
|
56
|
+
# brazil_client = Client.using(:brazil).create!(:name => "Brazil Client")
|
57
|
+
# master_client = Client.create!(:name => "Master Client")
|
58
|
+
#
|
59
|
+
# item_brazil = Item.using(:brazil).create!(:name => "Brazil Item", :client => brazil_client)
|
60
|
+
# item_master = Item.create!(:name => "Master Item", :client => master_client)
|
61
|
+
# c = Client.using(:brazil).find_by_name("Brazil Client")
|
62
|
+
# Client.using(:master).create!(:name => "teste")
|
63
|
+
# c.items.should == [item_brazil]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "raising errors" do
|
68
|
+
it "should raise a error when you specify a shard that doesn't exist" do
|
69
|
+
lambda { User.using(:crazy_shard) }.should raise_error("Nonexistent Shard Name: crazy_shard")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "using a postgresql shard" do
|
75
|
+
after(:each) do
|
76
|
+
User.using(:postgresql_shard).delete_all
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should update the Arel Engine" do
|
80
|
+
User.using(:postgresql_shard).arel_engine.connection.adapter_name.should == "PostgreSQL"
|
81
|
+
User.using(:alone_shard).arel_engine.connection.adapter_name.should == "MySQL"
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should works with writes and reads" do
|
85
|
+
pending()
|
86
|
+
#u = User.using(:postgresql_shard).create!(:name => "PostgreSQL User")
|
87
|
+
# #User.using(:postgresql_shard).arel_table.columns.should == ""
|
88
|
+
# User.using(:postgresql_shard).scoped.should == ""
|
89
|
+
# User.using(:alone_shard).find(:all).should == []
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#replicated_model method" do
|
94
|
+
before(:each) do
|
95
|
+
Octopus.stub!(:env).and_return("production_replicated")
|
96
|
+
@proxy = Octopus::Proxy.new(Octopus.config())
|
97
|
+
#TODO - This is ugly, but is better than mocking
|
98
|
+
ActiveRecord::Base.class_eval("@@connection_proxy = nil")
|
99
|
+
#TODO - This is ugly, but is better than mocking
|
100
|
+
end
|
101
|
+
|
102
|
+
after(:each) do
|
103
|
+
#TODO - One little Kitten dies each time this code is executed.
|
104
|
+
ActiveRecord::Base.class_eval("@@connection_proxy = nil")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should be replicated" do
|
108
|
+
ActiveRecord::Base.connection_proxy.replicated.should be_true
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should mark the Cat model as replicated" do
|
112
|
+
Cat.all.should == []
|
113
|
+
ActiveRecord::Base.connection_proxy.replicated_models.first.should == "Cat"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#sharded_by method" do
|
118
|
+
it "should send all queries to the specify shard" do
|
119
|
+
pending()
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Octopus do
|
4
|
+
describe "#config method" do
|
5
|
+
it "should load shards.yml file to start working" do
|
6
|
+
Octopus.config().should be_kind_of(Hash)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#directory method" do
|
11
|
+
it "should return the directory that contains the shards.yml file" do
|
12
|
+
Octopus.directory().should == File.expand_path(File.dirname(__FILE__) + "/../")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Octopus::Proxy do
|
4
|
+
before(:each) do
|
5
|
+
@proxy = Octopus::Proxy.new(Octopus.config())
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "creating a new instance" do
|
9
|
+
it "should initialize all shards and groups" do
|
10
|
+
@proxy.shards.keys.to_set.should == [:postgresql_shard, :alone_shard, :aug2011, :canada, :brazil, :aug2009, :russia, :aug2010, :master].to_set
|
11
|
+
@proxy.groups.should == {:country_shards=>[:canada, :brazil, :russia], :history_shards=>[:aug2009, :aug2010, :aug2011]}
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should initialize the block attribute as false" do
|
15
|
+
@proxy.block.should be_false
|
16
|
+
end
|
17
|
+
it "should initialize replicated attribute as false" do
|
18
|
+
@proxy.replicated.should be_false
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "should raise error if you have duplicated shard names" do
|
22
|
+
before(:each) do
|
23
|
+
Octopus.stub!(:env).and_return("production_raise_error")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise the error" do
|
27
|
+
lambda { Octopus::Proxy.new(Octopus.config()) }.should raise_error("You have duplicated shard names!")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "returning the correct connection" do
|
33
|
+
describe "should return the shard name" do
|
34
|
+
it "when current_shard is empty" do
|
35
|
+
@proxy.shard_name.should == :master
|
36
|
+
end
|
37
|
+
|
38
|
+
it "when current_shard is a single shard" do
|
39
|
+
@proxy.current_shard = :canada
|
40
|
+
@proxy.shard_name.should == :canada
|
41
|
+
end
|
42
|
+
|
43
|
+
it "when current_shard is more than one shard" do
|
44
|
+
@proxy.current_shard = [:russia, :brazil]
|
45
|
+
@proxy.shard_name.should == :russia
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "should return the connection based on shard_name" do
|
50
|
+
it "when current_shard is empty" do
|
51
|
+
@proxy.select_connection().should == @proxy.shards[:master].connection()
|
52
|
+
end
|
53
|
+
|
54
|
+
it "when current_shard is a single shard" do
|
55
|
+
@proxy.current_shard = :canada
|
56
|
+
@proxy.select_connection().should == @proxy.shards[:canada].connection()
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "when the database is replicated" do
|
62
|
+
before(:each) do
|
63
|
+
Octopus.stub!(:env).and_return("production_replicated")
|
64
|
+
@proxy = Octopus::Proxy.new(Octopus.config())
|
65
|
+
#TODO - THIS IS SO UGLY
|
66
|
+
ActiveRecord::Base.class_eval("@@connection_proxy = nil")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have the replicated attribute as true" do
|
70
|
+
@proxy.replicated.should be_true
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should initialize the list of shards" do
|
74
|
+
@proxy.slaves_list.should == ["slave1", "slave2", "slave3", "slave4"]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should send all writes queries to master" do
|
78
|
+
u = User.create!(:name => "Replicated")
|
79
|
+
|
80
|
+
[:slave4, :slave1, :slave2, :slave3].each do |sym|
|
81
|
+
User.using(sym).count.should == 0
|
82
|
+
end
|
83
|
+
|
84
|
+
User.using(:master).count.should == 1
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should send read queries to slaves, using a round robin algorithm" do
|
88
|
+
u = Cat.create!(:name => "master")
|
89
|
+
c = Client.create!(:name => "client_master")
|
90
|
+
|
91
|
+
[:slave4, :slave1, :slave2, :slave3].each do |sym|
|
92
|
+
Cat.using(sym).create!(:name => "Replicated_#{sym}")
|
93
|
+
end
|
94
|
+
|
95
|
+
Client.find(:first).should_not be_nil
|
96
|
+
Cat.find(:first).name.should == "Replicated_slave1"
|
97
|
+
Client.find(:first).should_not be_nil
|
98
|
+
Cat.find(:first).name.should == "Replicated_slave2"
|
99
|
+
Client.find(:first).should_not be_nil
|
100
|
+
Cat.find(:first).name.should == "Replicated_slave3"
|
101
|
+
Client.find(:first).should_not be_nil
|
102
|
+
Cat.find(:first).name.should == "Replicated_slave4"
|
103
|
+
Client.find(:first).should_not be_nil
|
104
|
+
Cat.find(:first).name.should == "Replicated_slave1"
|
105
|
+
Client.find(:first).should_not be_nil
|
106
|
+
|
107
|
+
[:slave4, :slave1, :slave2, :slave3].each do |sym|
|
108
|
+
Cat.using(sym).find_by_name("master").should be_false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|