makara 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +28 -0
  7. data/CHANGELOG.md +27 -0
  8. data/Gemfile +18 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +278 -0
  11. data/Rakefile +9 -0
  12. data/gemfiles/ar30.gemfile +30 -0
  13. data/gemfiles/ar31.gemfile +29 -0
  14. data/gemfiles/ar32.gemfile +29 -0
  15. data/gemfiles/ar40.gemfile +15 -0
  16. data/gemfiles/ar41.gemfile +15 -0
  17. data/gemfiles/ar42.gemfile +15 -0
  18. data/lib/active_record/connection_adapters/jdbcmysql_makara_adapter.rb +25 -0
  19. data/lib/active_record/connection_adapters/jdbcpostgresql_makara_adapter.rb +25 -0
  20. data/lib/active_record/connection_adapters/makara_abstract_adapter.rb +209 -0
  21. data/lib/active_record/connection_adapters/makara_jdbcmysql_adapter.rb +25 -0
  22. data/lib/active_record/connection_adapters/makara_jdbcpostgresql_adapter.rb +25 -0
  23. data/lib/active_record/connection_adapters/makara_mysql2_adapter.rb +44 -0
  24. data/lib/active_record/connection_adapters/makara_postgresql_adapter.rb +44 -0
  25. data/lib/active_record/connection_adapters/mysql2_makara_adapter.rb +44 -0
  26. data/lib/active_record/connection_adapters/postgresql_makara_adapter.rb +44 -0
  27. data/lib/makara.rb +25 -0
  28. data/lib/makara/cache.rb +53 -0
  29. data/lib/makara/cache/memory_store.rb +28 -0
  30. data/lib/makara/cache/noop_store.rb +15 -0
  31. data/lib/makara/config_parser.rb +200 -0
  32. data/lib/makara/connection_wrapper.rb +170 -0
  33. data/lib/makara/context.rb +46 -0
  34. data/lib/makara/error_handler.rb +39 -0
  35. data/lib/makara/errors/all_connections_blacklisted.rb +13 -0
  36. data/lib/makara/errors/blacklist_connection.rb +14 -0
  37. data/lib/makara/errors/no_connections_available.rb +14 -0
  38. data/lib/makara/logging/logger.rb +23 -0
  39. data/lib/makara/logging/subscriber.rb +38 -0
  40. data/lib/makara/middleware.rb +109 -0
  41. data/lib/makara/pool.rb +188 -0
  42. data/lib/makara/proxy.rb +277 -0
  43. data/lib/makara/railtie.rb +14 -0
  44. data/lib/makara/version.rb +15 -0
  45. data/makara.gemspec +19 -0
  46. data/spec/active_record/connection_adapters/makara_abstract_adapter_error_handling_spec.rb +92 -0
  47. data/spec/active_record/connection_adapters/makara_abstract_adapter_spec.rb +114 -0
  48. data/spec/active_record/connection_adapters/makara_mysql2_adapter_spec.rb +183 -0
  49. data/spec/active_record/connection_adapters/makara_postgresql_adapter_spec.rb +121 -0
  50. data/spec/cache_spec.rb +59 -0
  51. data/spec/config_parser_spec.rb +102 -0
  52. data/spec/connection_wrapper_spec.rb +33 -0
  53. data/spec/context_spec.rb +107 -0
  54. data/spec/middleware_spec.rb +84 -0
  55. data/spec/pool_spec.rb +158 -0
  56. data/spec/proxy_spec.rb +182 -0
  57. data/spec/spec_helper.rb +46 -0
  58. data/spec/support/configurator.rb +13 -0
  59. data/spec/support/deep_dup.rb +12 -0
  60. data/spec/support/mock_objects.rb +67 -0
  61. data/spec/support/mysql2_database.yml +17 -0
  62. data/spec/support/mysql2_database_with_custom_errors.yml +17 -0
  63. data/spec/support/pool_extensions.rb +14 -0
  64. data/spec/support/postgresql_database.yml +13 -0
  65. data/spec/support/proxy_extensions.rb +33 -0
  66. data/spec/support/schema.rb +7 -0
  67. 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
@@ -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
@@ -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,15 @@
1
+ module Makara
2
+ module Cache
3
+ class NoopStore
4
+
5
+ def read(key)
6
+ nil
7
+ end
8
+
9
+ def write(key, value, options = {})
10
+ nil
11
+ end
12
+
13
+ end
14
+ end
15
+ 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