makara 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +28 -0
- data/CHANGELOG.md +27 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +278 -0
- data/Rakefile +9 -0
- data/gemfiles/ar30.gemfile +30 -0
- data/gemfiles/ar31.gemfile +29 -0
- data/gemfiles/ar32.gemfile +29 -0
- data/gemfiles/ar40.gemfile +15 -0
- data/gemfiles/ar41.gemfile +15 -0
- data/gemfiles/ar42.gemfile +15 -0
- data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +209 -0
- data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +25 -0
- data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +44 -0
- data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +44 -0
- data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +44 -0
- data/lib/makara.rb +25 -0
- data/lib/makara/cache.rb +53 -0
- data/lib/makara/cache/memory_store.rb +28 -0
- data/lib/makara/cache/noop_store.rb +15 -0
- data/lib/makara/config_parser.rb +200 -0
- data/lib/makara/connection_wrapper.rb +170 -0
- data/lib/makara/context.rb +46 -0
- data/lib/makara/error_handler.rb +39 -0
- data/lib/makara/errors/all_connections_blacklisted.rb +13 -0
- data/lib/makara/errors/blacklist_connection.rb +14 -0
- data/lib/makara/errors/no_connections_available.rb +14 -0
- data/lib/makara/logging/logger.rb +23 -0
- data/lib/makara/logging/subscriber.rb +38 -0
- data/lib/makara/middleware.rb +109 -0
- data/lib/makara/pool.rb +188 -0
- data/lib/makara/proxy.rb +277 -0
- data/lib/makara/railtie.rb +14 -0
- data/lib/makara/version.rb +15 -0
- data/makara.gemspec +19 -0
- data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +92 -0
- data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +114 -0
- data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +183 -0
- data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +121 -0
- data/spec/cache_spec.rb +59 -0
- data/spec/config_parser_spec.rb +102 -0
- data/spec/connection_wrapper_spec.rb +33 -0
- data/spec/context_spec.rb +107 -0
- data/spec/middleware_spec.rb +84 -0
- data/spec/pool_spec.rb +158 -0
- data/spec/proxy_spec.rb +182 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/configurator.rb +13 -0
- data/spec/support/deep_dup.rb +12 -0
- data/spec/support/mock_objects.rb +67 -0
- data/spec/support/mysql2_database.yml +17 -0
- data/spec/support/mysql2_database_with_custom_errors.yml +17 -0
- data/spec/support/pool_extensions.rb +14 -0
- data/spec/support/postgresql_database.yml +13 -0
- data/spec/support/proxy_extensions.rb +33 -0
- data/spec/support/schema.rb +7 -0
- metadata +144 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_record/connection_adapters/makara_abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
if ActiveRecord::VERSION::MAJOR >= 4
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module ConnectionHandling
|
8
|
+
def makara_postgresql_connection(config)
|
9
|
+
ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
else
|
15
|
+
|
16
|
+
module ActiveRecord
|
17
|
+
class Base
|
18
|
+
def self.makara_postgresql_connection(config)
|
19
|
+
ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
module ActiveRecord
|
27
|
+
module ConnectionAdapters
|
28
|
+
class MakaraPostgreSQLAdapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def visitor_for(*args)
|
32
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.visitor_for(*args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def active_record_connection_for(config)
|
39
|
+
::ActiveRecord::Base.postgresql_connection(config)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_record/connection_adapters/makara_abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/mysql2_adapter'
|
3
|
+
|
4
|
+
if ActiveRecord::VERSION::MAJOR >= 4
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module ConnectionHandling
|
8
|
+
def mysql2_makara_connection(config)
|
9
|
+
ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter.new(config)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
else
|
15
|
+
|
16
|
+
module ActiveRecord
|
17
|
+
class Base
|
18
|
+
def self.mysql2_makara_connection(config)
|
19
|
+
ActiveRecord::ConnectionAdapters::MakaraMysql2Adapter.new(config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
module ActiveRecord
|
27
|
+
module ConnectionAdapters
|
28
|
+
class MakaraMysql2Adapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def visitor_for(*args)
|
32
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.visitor_for(*args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def active_record_connection_for(config)
|
39
|
+
::ActiveRecord::Base.mysql2_connection(config)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_record/connection_adapters/makara_abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
3
|
+
|
4
|
+
if ActiveRecord::VERSION::MAJOR >= 4
|
5
|
+
|
6
|
+
module ActiveRecord
|
7
|
+
module ConnectionHandling
|
8
|
+
def postgresql_makara_connection(config)
|
9
|
+
ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
else
|
15
|
+
|
16
|
+
module ActiveRecord
|
17
|
+
class Base
|
18
|
+
def self.postgresql_makara_connection(config)
|
19
|
+
ActiveRecord::ConnectionAdapters::MakaraPostgreSQLAdapter.new(config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
module ActiveRecord
|
27
|
+
module ConnectionAdapters
|
28
|
+
class MakaraPostgreSQLAdapter < ActiveRecord::ConnectionAdapters::MakaraAbstractAdapter
|
29
|
+
|
30
|
+
class << self
|
31
|
+
def visitor_for(*args)
|
32
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.visitor_for(*args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def active_record_connection_for(config)
|
39
|
+
::ActiveRecord::Base.postgresql_connection(config)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/makara.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'makara/version'
|
2
|
+
require 'makara/railtie' if defined?(Rails)
|
3
|
+
module Makara
|
4
|
+
|
5
|
+
autoload :Cache, 'makara/cache'
|
6
|
+
autoload :ConfigParser, 'makara/config_parser'
|
7
|
+
autoload :ConnectionWrapper, 'makara/connection_wrapper'
|
8
|
+
autoload :Context, 'makara/context'
|
9
|
+
autoload :ErrorHandler, 'makara/error_handler'
|
10
|
+
autoload :Middleware, 'makara/middleware'
|
11
|
+
autoload :Pool, 'makara/pool'
|
12
|
+
autoload :Proxy, 'makara/proxy'
|
13
|
+
|
14
|
+
module Errors
|
15
|
+
autoload :AllConnectionsBlacklisted, 'makara/errors/all_connections_blacklisted'
|
16
|
+
autoload :BlacklistConnection, 'makara/errors/blacklist_connection'
|
17
|
+
autoload :NoConnectionsAvailable, 'makara/errors/no_connections_available'
|
18
|
+
end
|
19
|
+
|
20
|
+
module Logging
|
21
|
+
autoload :Logger, 'makara/logging/logger'
|
22
|
+
autoload :Subscriber, 'makara/logging/subscriber'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/makara/cache.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'active_support/core_ext/object/try'
|
2
|
+
|
3
|
+
# The Makara Cache should have access to your centralized cache store.
|
4
|
+
# It serves the purpose of storing the Makara::Context across requests, servers, etc.
|
5
|
+
|
6
|
+
module Makara
|
7
|
+
module Cache
|
8
|
+
|
9
|
+
autoload :MemoryStore, 'makara/cache/memory_store'
|
10
|
+
autoload :NoopStore, 'makara/cache/noop_store'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def store=(store)
|
15
|
+
@store = store
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(key)
|
19
|
+
store.try(:read, key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def write(key, value, ttl)
|
23
|
+
store.try(:write, key, value, :expires_in => ttl.to_i)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def store
|
29
|
+
case @store
|
30
|
+
when :noop, :null
|
31
|
+
@store = Makara::Cache::NoopStore.new
|
32
|
+
when :memory
|
33
|
+
@store = Makara::Cache::MemoryStore.new
|
34
|
+
else
|
35
|
+
if defined?(Rails)
|
36
|
+
|
37
|
+
# in AR3 RAILS_CACHE may not be loaded if the full env is not present
|
38
|
+
# Rails.cache will throw an error because of it.
|
39
|
+
if ActiveRecord::VERSION::MAJOR < 4
|
40
|
+
@store ||= Rails.cache if defined?(RAILS_CACHE)
|
41
|
+
else
|
42
|
+
@store ||= Rails.cache if defined?(Rails)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@store
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Makara
|
2
|
+
module Cache
|
3
|
+
class MemoryStore
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@data = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def read(key)
|
10
|
+
clean
|
11
|
+
@data[key].try(:[], 0)
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(key, value, options = {})
|
15
|
+
clean
|
16
|
+
@data[key] = [value, Time.now.to_i + (options[:expires_in] || 5).to_i]
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def clean
|
23
|
+
@data.delete_if{|k,v| v[1] <= Time.now.to_i }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
require 'active_support/core_ext/hash/except'
|
4
|
+
|
5
|
+
# Convenience methods to grab subconfigs out of the primary configuration.
|
6
|
+
# Provides a way to generate a consistent ID based on a unique config.
|
7
|
+
# Makara configs should be formatted like so:
|
8
|
+
# --
|
9
|
+
# top_level: 'variable'
|
10
|
+
# another: 'top level variable'
|
11
|
+
# makara:
|
12
|
+
# master_ttl: 3
|
13
|
+
# blacklist_duration: 20
|
14
|
+
# connections:
|
15
|
+
# - role: 'master'
|
16
|
+
# - role: 'slave'
|
17
|
+
# - role: 'slave'
|
18
|
+
# name: 'slave2'
|
19
|
+
|
20
|
+
module Makara
|
21
|
+
class ConfigParser
|
22
|
+
|
23
|
+
DEFAULTS = {
|
24
|
+
:master_ttl => 5,
|
25
|
+
:blacklist_duration => 30,
|
26
|
+
:sticky => true
|
27
|
+
}
|
28
|
+
|
29
|
+
# ConnectionUrlResolver is borrowed from Rails 4-2 since its location and implementation
|
30
|
+
# vary slightly among Rails versions, but the behavior is the same. Thus, borrowing the
|
31
|
+
# class should be the most future-safe way to parse a database url.
|
32
|
+
#
|
33
|
+
# Expands a connection string into a hash.
|
34
|
+
class ConnectionUrlResolver # :nodoc:
|
35
|
+
|
36
|
+
# == Example
|
37
|
+
#
|
38
|
+
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
|
39
|
+
# ConnectionUrlResolver.new(url).to_hash
|
40
|
+
# # => {
|
41
|
+
# "adapter" => "postgresql",
|
42
|
+
# "host" => "localhost",
|
43
|
+
# "port" => 9000,
|
44
|
+
# "database" => "foo_test",
|
45
|
+
# "username" => "foo",
|
46
|
+
# "password" => "bar",
|
47
|
+
# "pool" => "5",
|
48
|
+
# "timeout" => "3000"
|
49
|
+
# }
|
50
|
+
def initialize(url)
|
51
|
+
raise "Database URL cannot be empty" if url.blank?
|
52
|
+
@uri = URI.parse(url)
|
53
|
+
@adapter = @uri.scheme.tr('-', '_')
|
54
|
+
@adapter = "postgresql" if @adapter == "postgres"
|
55
|
+
|
56
|
+
if @uri.opaque
|
57
|
+
@uri.opaque, @query = @uri.opaque.split('?', 2)
|
58
|
+
else
|
59
|
+
@query = @uri.query
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Converts the given URL to a full connection hash.
|
64
|
+
def to_hash
|
65
|
+
config = raw_config.reject { |_,value| value.blank? }
|
66
|
+
config.map { |key,value| config[key] = URI.unescape(value) if value.is_a? String }
|
67
|
+
config
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def uri
|
73
|
+
@uri
|
74
|
+
end
|
75
|
+
|
76
|
+
# Converts the query parameters of the URI into a hash.
|
77
|
+
#
|
78
|
+
# "localhost?pool=5&reaping_frequency=2"
|
79
|
+
# # => { "pool" => "5", "reaping_frequency" => "2" }
|
80
|
+
#
|
81
|
+
# returns empty hash if no query present.
|
82
|
+
#
|
83
|
+
# "localhost"
|
84
|
+
# # => {}
|
85
|
+
def query_hash
|
86
|
+
Hash[(@query || '').split("&").map { |pair| pair.split("=") }]
|
87
|
+
end
|
88
|
+
|
89
|
+
def raw_config
|
90
|
+
if uri.opaque
|
91
|
+
query_hash.merge({
|
92
|
+
"adapter" => @adapter,
|
93
|
+
"database" => uri.opaque })
|
94
|
+
else
|
95
|
+
query_hash.merge({
|
96
|
+
"adapter" => @adapter,
|
97
|
+
"username" => uri.user,
|
98
|
+
"password" => uri.password,
|
99
|
+
"port" => uri.port,
|
100
|
+
"database" => database_from_path,
|
101
|
+
"host" => uri.host })
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns name of the database.
|
106
|
+
def database_from_path
|
107
|
+
if @adapter == 'sqlite3'
|
108
|
+
# 'sqlite3:/foo' is absolute, because that makes sense. The
|
109
|
+
# corresponding relative version, 'sqlite3:foo', is handled
|
110
|
+
# elsewhere, as an "opaque".
|
111
|
+
|
112
|
+
uri.path
|
113
|
+
else
|
114
|
+
# Only SQLite uses a filename as the "database" name; for
|
115
|
+
# anything else, a leading slash would be silly.
|
116
|
+
|
117
|
+
uri.path.sub(%r{^/}, "")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# NOTE: url format must be, e.g.
|
123
|
+
# url: mysql2://...
|
124
|
+
# NOT
|
125
|
+
# url: mysql2_makara://...
|
126
|
+
# since the '_' in the protocol (mysql2_makara) makes the URI invalid
|
127
|
+
# NOTE: Does not use ENV['DATABASE_URL']
|
128
|
+
def self.merge_and_resolve_default_url_config(config)
|
129
|
+
if ENV['DATABASE_URL']
|
130
|
+
Logging::Logger.log "Please rename DATABASE_URL to use in the database.yml", :warn
|
131
|
+
end
|
132
|
+
return config unless config.key?(:url)
|
133
|
+
url = config[:url]
|
134
|
+
url_config = ConnectionUrlResolver.new(url).to_hash
|
135
|
+
url_config = url_config.symbolize_keys
|
136
|
+
url_config.delete(:adapter)
|
137
|
+
config.delete(:url)
|
138
|
+
config.update(url_config)
|
139
|
+
end
|
140
|
+
|
141
|
+
attr_reader :makara_config
|
142
|
+
|
143
|
+
def initialize(config)
|
144
|
+
@config = config.symbolize_keys
|
145
|
+
@makara_config = DEFAULTS.merge(@config[:makara] || {})
|
146
|
+
@makara_config = @makara_config.symbolize_keys
|
147
|
+
@id = @makara_config[:id]
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
def id
|
152
|
+
@id ||= begin
|
153
|
+
sorted = recursive_sort(@config)
|
154
|
+
Digest::MD5.hexdigest(sorted.to_s)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
def master_configs
|
160
|
+
all_configs.
|
161
|
+
select{|config| config[:role] == 'master' }.
|
162
|
+
map{|config| config.except(:role) }
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def slave_configs
|
167
|
+
all_configs.
|
168
|
+
reject{|config| config[:role] == 'master' }.
|
169
|
+
map{|config| config.except(:role) }
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
protected
|
174
|
+
|
175
|
+
|
176
|
+
def all_configs
|
177
|
+
@makara_config[:connections].map do |connection|
|
178
|
+
base_config.merge(connection.symbolize_keys)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
def base_config
|
184
|
+
@base_config ||= DEFAULTS.merge(@config).except(:makara)
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def recursive_sort(thing)
|
189
|
+
return thing.to_s unless thing.include?(Enumerable)
|
190
|
+
|
191
|
+
thing.map do |part|
|
192
|
+
recursive_sort(part)
|
193
|
+
end
|
194
|
+
|
195
|
+
thing.sort_by(&:to_s)
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|