beetle 0.3.0.rc.7 → 0.3.0.rc.8
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/beetle.gemspec +15 -14
- data/features/redis_auto_failover.feature +4 -0
- data/features/step_definitions/redis_auto_failover_steps.rb +6 -0
- data/features/support/test_daemons/redis_configuration_server.rb +36 -1
- data/lib/beetle.rb +1 -0
- data/lib/beetle/commands/configuration_server.rb +6 -1
- data/lib/beetle/redis_configuration_http_server.rb +98 -0
- data/lib/beetle/redis_configuration_server.rb +21 -0
- data/lib/beetle/redis_server_info.rb +3 -2
- data/lib/beetle/version.rb +1 -1
- data/script/console~ +2 -0
- data/test/beetle/redis_configuration_server_test.rb +4 -0
- data/test/beetle/redis_ext_test.rb +7 -1
- metadata +34 -17
- data/examples/test_publisher.rb +0 -32
data/beetle.gemspec
CHANGED
@@ -32,19 +32,20 @@ Gem::Specification.new do |s|
|
|
32
32
|
INFO
|
33
33
|
|
34
34
|
s.specification_version = 3
|
35
|
-
s.add_runtime_dependency("uuid4r",
|
36
|
-
s.add_runtime_dependency("bunny",
|
37
|
-
s.add_runtime_dependency("redis",
|
38
|
-
s.add_runtime_dependency("hiredis",
|
39
|
-
s.add_runtime_dependency("amq-client",
|
40
|
-
s.add_runtime_dependency("amq-protocol",
|
41
|
-
s.add_runtime_dependency("amqp",
|
42
|
-
s.add_runtime_dependency("activesupport",
|
43
|
-
s.add_runtime_dependency("
|
44
|
-
s.
|
45
|
-
s.add_development_dependency("
|
46
|
-
s.add_development_dependency("
|
47
|
-
s.add_development_dependency("
|
48
|
-
s.add_development_dependency("
|
35
|
+
s.add_runtime_dependency("uuid4r", [">= 0.1.2"])
|
36
|
+
s.add_runtime_dependency("bunny", ["= 0.7.8"])
|
37
|
+
s.add_runtime_dependency("redis", ["= 2.2.2"])
|
38
|
+
s.add_runtime_dependency("hiredis", ["= 0.3.2"])
|
39
|
+
s.add_runtime_dependency("amq-client", ["= 0.8.3"])
|
40
|
+
s.add_runtime_dependency("amq-protocol", ["= 0.8.1"])
|
41
|
+
s.add_runtime_dependency("amqp", ["= 0.8.0"])
|
42
|
+
s.add_runtime_dependency("activesupport", [">= 2.3.4"])
|
43
|
+
s.add_runtime_dependency("eventmachine_httpserver", [">= 0.2.1"])
|
44
|
+
s.add_runtime_dependency("daemons", [">= 1.0.10"])
|
45
|
+
s.add_development_dependency("rake", [">= 0.8.7"])
|
46
|
+
s.add_development_dependency("mocha", [">= 0"])
|
47
|
+
s.add_development_dependency("rcov", [">= 0"])
|
48
|
+
s.add_development_dependency("cucumber", [">= 0.7.2"])
|
49
|
+
s.add_development_dependency("daemon_controller", [">= 0"])
|
49
50
|
end
|
50
51
|
|
@@ -103,3 +103,7 @@ Feature: Redis auto failover
|
|
103
103
|
And an old redis master file for "rc-client-1" with master "redis-1" exists
|
104
104
|
And a redis configuration client "rc-client-1" using redis servers "redis-1,redis-2" exists
|
105
105
|
Then the redis master of "rc-client-1" should be "redis-1"
|
106
|
+
|
107
|
+
Scenario: Redis configuation server should embed a http server
|
108
|
+
Given a redis configuration server using redis servers "redis-1,redis-2" with clients "rc-client-1,rc-client-2" exists
|
109
|
+
Then the redis configuration server should answer http requests
|
@@ -132,3 +132,9 @@ Then /^a system notification for no slave available to become new master should
|
|
132
132
|
text = "Redis master could not be switched, no slave available to become new master"
|
133
133
|
assert_match /#{text}/, File.readlines(system_notification_log_path).last
|
134
134
|
end
|
135
|
+
|
136
|
+
Then /^the redis configuration server should answer http requests$/ do
|
137
|
+
TestDaemons::RedisConfigurationServer.answers_text_requests?
|
138
|
+
TestDaemons::RedisConfigurationServer.answers_html_requests?
|
139
|
+
TestDaemons::RedisConfigurationServer.answers_json_requests?
|
140
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'daemon_controller'
|
2
|
+
require 'net/http'
|
2
3
|
|
3
4
|
module TestDaemons
|
4
5
|
class RedisConfigurationServer
|
@@ -25,7 +26,7 @@ module TestDaemons
|
|
25
26
|
DaemonController.new(
|
26
27
|
:identifier => "Redis configuration test server",
|
27
28
|
:start_command => "ruby bin/beetle configuration_server start -- -v --redis-master-file #{redis_master_file} --redis-servers #{@@redis_servers} #{clients_parameter_string} --redis-retry-interval 1 --pid-dir #{tmp_path} --amqp-servers 127.0.0.1:5672",
|
28
|
-
:ping_command => lambda{
|
29
|
+
:ping_command => lambda{ answers_text_requests? },
|
29
30
|
:pid_file => pid_file,
|
30
31
|
:log_file => log_file,
|
31
32
|
:start_timeout => 5
|
@@ -48,5 +49,39 @@ module TestDaemons
|
|
48
49
|
File.expand_path(File.dirname(__FILE__) + "/../../../tmp")
|
49
50
|
end
|
50
51
|
|
52
|
+
def self.answers_text_requests?
|
53
|
+
response = get_status("/.txt", "text/plain")
|
54
|
+
response.code == '200' &&
|
55
|
+
response.content_type == "text/plain"
|
56
|
+
rescue
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.answers_json_requests?
|
61
|
+
response = get_status("/.json", "application/json")
|
62
|
+
response.code == '200' &&
|
63
|
+
response.content_type == "application/json"
|
64
|
+
rescue
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.answers_html_requests?
|
69
|
+
response = get_status("/", "text/html")
|
70
|
+
response.code == '200' &&
|
71
|
+
response.content_type == "txt/html"
|
72
|
+
rescue
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.get_status(path, content_type)
|
77
|
+
uri = URI.parse("http://127.0.0.1:8080#{path}")
|
78
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
79
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
80
|
+
request['Accept'] = content_type
|
81
|
+
response = http.request(request)
|
82
|
+
# $stderr.puts response.content_type
|
83
|
+
# $stderr.puts response.body
|
84
|
+
response
|
85
|
+
end
|
51
86
|
end
|
52
87
|
end
|
data/lib/beetle.rb
CHANGED
@@ -77,7 +77,12 @@ module Beetle
|
|
77
77
|
end
|
78
78
|
|
79
79
|
Daemons.run_proc("redis_configuration_server", :log_output => true, :dir_mode => dir_mode, :dir => dir) do
|
80
|
-
Beetle::RedisConfigurationServer.new
|
80
|
+
config_server = Beetle::RedisConfigurationServer.new
|
81
|
+
Beetle::RedisConfigurationHttpServer.config_server = config_server
|
82
|
+
EM.run do
|
83
|
+
config_server.start
|
84
|
+
EM.start_server '0.0.0.0', 8080, Beetle::RedisConfigurationHttpServer
|
85
|
+
end
|
81
86
|
end
|
82
87
|
end
|
83
88
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'evma_httpserver'
|
2
|
+
|
3
|
+
module Beetle
|
4
|
+
class RedisConfigurationHttpServer < EM::Connection
|
5
|
+
include EM::HttpServer
|
6
|
+
|
7
|
+
def post_init
|
8
|
+
super
|
9
|
+
no_environment_strings
|
10
|
+
end
|
11
|
+
|
12
|
+
cattr_accessor :config_server
|
13
|
+
|
14
|
+
def process_http_request
|
15
|
+
# the http request details are available via the following instance variables:
|
16
|
+
# @http_protocol
|
17
|
+
# @http_request_method
|
18
|
+
# @http_cookie
|
19
|
+
# @http_if_none_match
|
20
|
+
# @http_content_type
|
21
|
+
# @http_path_info
|
22
|
+
# @http_request_uri
|
23
|
+
# @http_query_string
|
24
|
+
# @http_post_content
|
25
|
+
# @http_headers
|
26
|
+
response = EM::DelegatedHttpResponse.new(self)
|
27
|
+
# headers = @http_headers.split("\0").inject({}){|h, s| (s =~ /^([^:]+): (.*)$/ && (h[$1] = $2)); h }
|
28
|
+
|
29
|
+
case @http_request_uri
|
30
|
+
when '/', '/.html'
|
31
|
+
response.content_type 'text/html'
|
32
|
+
server_status(response, "html")
|
33
|
+
when "/.json"
|
34
|
+
response.content_type 'application/json'
|
35
|
+
server_status(response, "json")
|
36
|
+
when "/.txt"
|
37
|
+
response.content_type 'text/plain'
|
38
|
+
server_status(response, "plain")
|
39
|
+
else
|
40
|
+
not_found(response)
|
41
|
+
end
|
42
|
+
response.send_response
|
43
|
+
end
|
44
|
+
|
45
|
+
def server_status(response, type)
|
46
|
+
response.status = 200
|
47
|
+
status = config_server.status
|
48
|
+
response.content =
|
49
|
+
case type
|
50
|
+
when "plain"
|
51
|
+
plain_text_response(status)
|
52
|
+
when "json"
|
53
|
+
status.to_json
|
54
|
+
when "html"
|
55
|
+
html_response(status)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def plain_text_response(status)
|
60
|
+
status.keys.sort_by{|k| k.to_s}.map do |k|
|
61
|
+
name = k.to_s # .split('_').join(" ")
|
62
|
+
if (value = status[k]).is_a?(Array)
|
63
|
+
value = value.join(", ")
|
64
|
+
end
|
65
|
+
"#{name}: #{value}"
|
66
|
+
end.join("\n")
|
67
|
+
end
|
68
|
+
|
69
|
+
def html_response(status)
|
70
|
+
b = "<!doctype html>\n"
|
71
|
+
b << "<html><head><title>Beetle Configuration Server Status</title>#{html_styles(status)}</head>"
|
72
|
+
b << "<body><h1>Beetle Configuration Server Status</h1><table cellspacing=0>\n"
|
73
|
+
plain_text_response(status).split("\n").compact.each do |row|
|
74
|
+
row =~/(^[^:]+): (.*)$/
|
75
|
+
b << "<tr><td>#{$1}</td><td>#{$2}</td></tr>\n"
|
76
|
+
end
|
77
|
+
b << "</table></body></html>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def html_styles(status)
|
81
|
+
warn_color = status[:redis_master_available?] ? "#5780b2" : "#A52A2A"
|
82
|
+
<<"EOS"
|
83
|
+
<style media="screen" type="text/css">
|
84
|
+
html { font: 1.25em/1.5 arial, sans-serif;}
|
85
|
+
body { margin: 1em; }
|
86
|
+
table tr:nth-child(2n+1){ background:#fff; }
|
87
|
+
td { padding: 0.1em 0.2em; }
|
88
|
+
h1 { color: #{warn_color}; margin-bottom: 0.2em;}
|
89
|
+
</style>
|
90
|
+
EOS
|
91
|
+
end
|
92
|
+
|
93
|
+
def not_found(response)
|
94
|
+
response.content_type 'text/plain'
|
95
|
+
response.status = 404
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -21,6 +21,9 @@ module Beetle
|
|
21
21
|
# the current token used to detect correct message order
|
22
22
|
attr_reader :current_token
|
23
23
|
|
24
|
+
# the list of known client ids
|
25
|
+
attr_reader :client_ids
|
26
|
+
|
24
27
|
def initialize #:nodoc:
|
25
28
|
@client_ids = Set.new(config.redis_configuration_client_ids.split(","))
|
26
29
|
@current_token = (Time.now.to_f * 1000).to_i
|
@@ -43,6 +46,19 @@ module Beetle
|
|
43
46
|
beetle.config
|
44
47
|
end
|
45
48
|
|
49
|
+
# returns a hash describing the current server status
|
50
|
+
def status
|
51
|
+
{
|
52
|
+
:beetle_version => Beetle::VERSION,
|
53
|
+
:configured_brokers => config.servers.split(/\s*,\s*/),
|
54
|
+
:configured_client_ids => client_ids.to_a.sort,
|
55
|
+
:configured_redis_servers => config.redis_servers.split(/\s*,\s*/),
|
56
|
+
:redis_master => current_master.try(:server).to_s,
|
57
|
+
:redis_master_available? => master_available?,
|
58
|
+
:redis_slaves_available => available_slaves.map(&:server),
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
46
62
|
# start watching redis
|
47
63
|
def start
|
48
64
|
verify_redis_master_file_string
|
@@ -126,6 +142,11 @@ module Beetle
|
|
126
142
|
redis.masters.include?(current_master)
|
127
143
|
end
|
128
144
|
|
145
|
+
# list of available redis slaves
|
146
|
+
def available_slaves
|
147
|
+
redis.slaves
|
148
|
+
end
|
149
|
+
|
129
150
|
private
|
130
151
|
|
131
152
|
def check_redis_configuration
|
@@ -52,12 +52,13 @@ module Beetle
|
|
52
52
|
masters.first
|
53
53
|
end
|
54
54
|
|
55
|
-
|
56
|
-
|
55
|
+
# check whether all redis servers are up and configured
|
57
56
|
def master_and_slaves_reachable?
|
58
57
|
masters.size == 1 && slaves.size == instances.size - 1
|
59
58
|
end
|
60
59
|
|
60
|
+
private
|
61
|
+
|
61
62
|
def reset
|
62
63
|
@server_info = Hash.new {|h,k| h[k]= []}
|
63
64
|
end
|
data/lib/beetle/version.rb
CHANGED
data/script/console~
ADDED
@@ -59,6 +59,10 @@ module Beetle
|
|
59
59
|
@server.beetle.expects(:publish).with(:reconfigure, payload)
|
60
60
|
@server.master_available!
|
61
61
|
end
|
62
|
+
|
63
|
+
test "should be able to report current status" do
|
64
|
+
assert @server.status.is_a?(Hash)
|
65
|
+
end
|
62
66
|
end
|
63
67
|
|
64
68
|
class RedisConfigurationServerInvalidationTest < Test::Unit::TestCase
|
@@ -68,11 +68,17 @@ module Beetle
|
|
68
68
|
|
69
69
|
# if this test fails after upgrading redis, we can probably remove
|
70
70
|
# our custom shutdown method from redis_ext.rb
|
71
|
-
test "redis shutdown implementation is broken" do
|
71
|
+
test "orginal redis shutdown implementation is broken" do
|
72
72
|
@r.client.expects(:call_without_reply).with([:shutdown]).once
|
73
73
|
@r.client.expects(:disconnect).never
|
74
74
|
@r.broken_shutdown
|
75
75
|
end
|
76
|
+
|
77
|
+
test "patched redis shutdown implementation should call :shutdown and rescue Errno::ECONNREFUSED" do
|
78
|
+
@r.client.expects(:call).with([:shutdown]).once.raises(Errno::ECONNREFUSED)
|
79
|
+
@r.client.expects(:disconnect).once
|
80
|
+
@r.shutdown
|
81
|
+
end
|
76
82
|
end
|
77
83
|
|
78
84
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: beetle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15424053
|
5
5
|
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 3
|
9
9
|
- 0
|
10
10
|
- rc
|
11
|
-
-
|
12
|
-
version: 0.3.0.rc.
|
11
|
+
- 8
|
12
|
+
version: 0.3.0.rc.8
|
13
13
|
platform: ruby
|
14
14
|
authors:
|
15
15
|
- Stefan Kaes
|
@@ -21,7 +21,7 @@ autorequire:
|
|
21
21
|
bindir: bin
|
22
22
|
cert_chain: []
|
23
23
|
|
24
|
-
date: 2011-10-
|
24
|
+
date: 2011-10-29 00:00:00 +02:00
|
25
25
|
default_executable: beetle
|
26
26
|
dependencies:
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -153,9 +153,25 @@ dependencies:
|
|
153
153
|
type: :runtime
|
154
154
|
version_requirements: *id008
|
155
155
|
- !ruby/object:Gem::Dependency
|
156
|
-
name:
|
156
|
+
name: eventmachine_httpserver
|
157
157
|
prerelease: false
|
158
158
|
requirement: &id009 !ruby/object:Gem::Requirement
|
159
|
+
none: false
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
hash: 21
|
164
|
+
segments:
|
165
|
+
- 0
|
166
|
+
- 2
|
167
|
+
- 1
|
168
|
+
version: 0.2.1
|
169
|
+
type: :runtime
|
170
|
+
version_requirements: *id009
|
171
|
+
- !ruby/object:Gem::Dependency
|
172
|
+
name: daemons
|
173
|
+
prerelease: false
|
174
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
159
175
|
none: false
|
160
176
|
requirements:
|
161
177
|
- - ">="
|
@@ -167,11 +183,11 @@ dependencies:
|
|
167
183
|
- 10
|
168
184
|
version: 1.0.10
|
169
185
|
type: :runtime
|
170
|
-
version_requirements: *
|
186
|
+
version_requirements: *id010
|
171
187
|
- !ruby/object:Gem::Dependency
|
172
188
|
name: rake
|
173
189
|
prerelease: false
|
174
|
-
requirement: &
|
190
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
175
191
|
none: false
|
176
192
|
requirements:
|
177
193
|
- - ">="
|
@@ -183,11 +199,11 @@ dependencies:
|
|
183
199
|
- 7
|
184
200
|
version: 0.8.7
|
185
201
|
type: :development
|
186
|
-
version_requirements: *
|
202
|
+
version_requirements: *id011
|
187
203
|
- !ruby/object:Gem::Dependency
|
188
204
|
name: mocha
|
189
205
|
prerelease: false
|
190
|
-
requirement: &
|
206
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
191
207
|
none: false
|
192
208
|
requirements:
|
193
209
|
- - ">="
|
@@ -197,11 +213,11 @@ dependencies:
|
|
197
213
|
- 0
|
198
214
|
version: "0"
|
199
215
|
type: :development
|
200
|
-
version_requirements: *
|
216
|
+
version_requirements: *id012
|
201
217
|
- !ruby/object:Gem::Dependency
|
202
218
|
name: rcov
|
203
219
|
prerelease: false
|
204
|
-
requirement: &
|
220
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
205
221
|
none: false
|
206
222
|
requirements:
|
207
223
|
- - ">="
|
@@ -211,11 +227,11 @@ dependencies:
|
|
211
227
|
- 0
|
212
228
|
version: "0"
|
213
229
|
type: :development
|
214
|
-
version_requirements: *
|
230
|
+
version_requirements: *id013
|
215
231
|
- !ruby/object:Gem::Dependency
|
216
232
|
name: cucumber
|
217
233
|
prerelease: false
|
218
|
-
requirement: &
|
234
|
+
requirement: &id014 !ruby/object:Gem::Requirement
|
219
235
|
none: false
|
220
236
|
requirements:
|
221
237
|
- - ">="
|
@@ -227,11 +243,11 @@ dependencies:
|
|
227
243
|
- 2
|
228
244
|
version: 0.7.2
|
229
245
|
type: :development
|
230
|
-
version_requirements: *
|
246
|
+
version_requirements: *id014
|
231
247
|
- !ruby/object:Gem::Dependency
|
232
248
|
name: daemon_controller
|
233
249
|
prerelease: false
|
234
|
-
requirement: &
|
250
|
+
requirement: &id015 !ruby/object:Gem::Requirement
|
235
251
|
none: false
|
236
252
|
requirements:
|
237
253
|
- - ">="
|
@@ -241,7 +257,7 @@ dependencies:
|
|
241
257
|
- 0
|
242
258
|
version: "0"
|
243
259
|
type: :development
|
244
|
-
version_requirements: *
|
260
|
+
version_requirements: *id015
|
245
261
|
description: A highly available, reliable messaging infrastructure
|
246
262
|
email: opensource@xing.com
|
247
263
|
executables:
|
@@ -266,7 +282,6 @@ files:
|
|
266
282
|
- examples/redundant.rb
|
267
283
|
- examples/rpc.rb
|
268
284
|
- examples/simple.rb
|
269
|
-
- examples/test_publisher.rb
|
270
285
|
- lib/beetle/base.rb
|
271
286
|
- lib/beetle/client.rb
|
272
287
|
- lib/beetle/commands/configuration_client.rb
|
@@ -280,6 +295,7 @@ files:
|
|
280
295
|
- lib/beetle/publisher.rb
|
281
296
|
- lib/beetle/r_c.rb
|
282
297
|
- lib/beetle/redis_configuration_client.rb
|
298
|
+
- lib/beetle/redis_configuration_http_server.rb
|
283
299
|
- lib/beetle/redis_configuration_server.rb
|
284
300
|
- lib/beetle/redis_ext.rb
|
285
301
|
- lib/beetle/redis_master_file.rb
|
@@ -298,6 +314,7 @@ files:
|
|
298
314
|
- features/support/test_daemons/redis_configuration_client.rb
|
299
315
|
- features/support/test_daemons/redis_configuration_server.rb
|
300
316
|
- script/console
|
317
|
+
- script/console~
|
301
318
|
- script/start_rabbit
|
302
319
|
- beetle.gemspec
|
303
320
|
- Rakefile
|
data/examples/test_publisher.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
# attempts.rb
|
2
|
-
# this example shows you how to use the exception limiting feature of beetle
|
3
|
-
# it allows you to control the number of retries your handler will go through
|
4
|
-
# with one message before giving up on it
|
5
|
-
#
|
6
|
-
# ! check the examples/README.rdoc for information on starting your redis/rabbit !
|
7
|
-
#
|
8
|
-
# start it with ruby attempts.rb
|
9
|
-
|
10
|
-
require "rubygems"
|
11
|
-
require File.expand_path("../lib/beetle", File.dirname(__FILE__))
|
12
|
-
require "eventmachine"
|
13
|
-
|
14
|
-
# set Beetle log level to info, less noisy than debug
|
15
|
-
Beetle.config.logger.level = Logger::INFO
|
16
|
-
|
17
|
-
# setup client
|
18
|
-
client = Beetle::Client.new
|
19
|
-
client.register_message(:test)
|
20
|
-
|
21
|
-
n = 0
|
22
|
-
EM.run do
|
23
|
-
EM.add_periodic_timer(0.1) do
|
24
|
-
data = (n+=1)
|
25
|
-
client.logger.info "publishing #{data}"
|
26
|
-
client.publish(:test, data)
|
27
|
-
end
|
28
|
-
trap("INT") do
|
29
|
-
client.stop_publishing
|
30
|
-
EM.stop_event_loop
|
31
|
-
end
|
32
|
-
end
|