beetle 1.0.4 → 2.0.0rc1

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.
@@ -1,157 +0,0 @@
1
- module Beetle
2
- # A RedisConfigurationClient is the subordinate part of beetle's
3
- # redis failover solution
4
- #
5
- # An instance of RedisConfigurationClient lives on every server that
6
- # hosts message consumers (worker server).
7
- #
8
- # It is responsible for determining an initial redis master and reacting to redis master
9
- # switches initiated by the RedisConfigurationServer.
10
- #
11
- # It will write the current redis master host:port string to a file specified via a
12
- # Configuration, which is then read by DeduplicationStore on redis access.
13
- #
14
- # Usually started via <tt>beetle configuration_client</tt> command.
15
- class RedisConfigurationClient
16
- include Logging
17
- include RedisMasterFile
18
-
19
- # Set a custom unique id for this instance. Must match an entry in
20
- # Configuration#redis_configuration_client_ids.
21
- attr_writer :id
22
-
23
- # The current redis master
24
- attr_reader :current_master
25
-
26
- # Unique id for this instance (defaults to the fully qualified hostname)
27
- def id
28
- @id ||= Beetle.hostname
29
- end
30
-
31
- def initialize #:nodoc:
32
- @current_token = nil
33
- MessageDispatcher.configuration_client = self
34
- end
35
-
36
- # determinines the initial redis master (if possible), then enters a messaging event
37
- # loop, reacting to failover related messages sent by RedisConfigurationServer.
38
- def start
39
- verify_redis_master_file_string
40
- client_started!
41
- logger.info "RedisConfigurationClient starting (client id: #{id})"
42
- determine_initial_master
43
- clear_redis_master_file unless current_master.try(:master?)
44
- logger.info "Listening"
45
- beetle.listen do
46
- EventMachine.add_periodic_timer(Beetle.config.redis_failover_client_heartbeat_interval) do
47
- heartbeat!
48
- end
49
- end
50
- end
51
-
52
- # called by the message dispatcher when a "pong" message from RedisConfigurationServer is received
53
- def ping(payload)
54
- token = payload["token"]
55
- logger.info "Received ping message with token '#{token}'"
56
- pong! if redeem_token(token)
57
- end
58
-
59
- # called by the message dispatcher when a "invalidate" message from RedisConfigurationServer is received
60
- def invalidate(payload)
61
- token = payload["token"]
62
- logger.info "Received invalidate message with token '#{token}'"
63
- invalidate! if redeem_token(token) && !current_master.try(:master?)
64
- end
65
-
66
- # called by the message dispatcher when a "reconfigure"" message from RedisConfigurationServer is received
67
- def reconfigure(payload)
68
- server = payload["server"]
69
- token = payload["token"]
70
- logger.info "Received reconfigure message with server '#{server}' and token '#{token}'"
71
- return unless redeem_token(token)
72
- unless server == read_redis_master_file
73
- new_master!(server)
74
- write_redis_master_file(server)
75
- end
76
- end
77
-
78
- # Beetle::Client instance for communication with the RedisConfigurationServer
79
- def beetle
80
- @beetle ||= build_beetle
81
- end
82
-
83
- def config #:nodoc:
84
- beetle.config
85
- end
86
-
87
- private
88
-
89
- def determine_initial_master
90
- if master_file_exists? && server = read_redis_master_file
91
- new_master!(server)
92
- end
93
- end
94
-
95
- def new_master!(server)
96
- @current_master = Redis.from_server_string(server, :timeout => 3)
97
- end
98
-
99
- def build_beetle
100
- system = Beetle.config.system_name
101
- Beetle::Client.new.configure :exchange => system, :auto_delete => true do |config|
102
- # messages sent
103
- config.message :pong
104
- config.message :client_started
105
- config.message :client_invalidated
106
- config.message :heartbeat
107
- # messages received
108
- config.message :ping
109
- config.message :invalidate
110
- config.message :reconfigure
111
- # queue setup
112
- config.queue :client, :key => 'ping', :amqp_name => "#{system}_configuration_client_#{id}"
113
- config.binding :client, :key => 'invalidate'
114
- config.binding :client, :key => 'reconfigure'
115
- config.handler :client, MessageDispatcher
116
- end
117
- end
118
-
119
- def redeem_token(token)
120
- @current_token = token if @current_token.nil? || token > @current_token
121
- token_valid = token >= @current_token
122
- logger.info "Ignored message (token was '#{token}', but expected to be >= '#{@current_token}')" unless token_valid
123
- token_valid
124
- end
125
-
126
- def pong!
127
- logger.info "Sending pong message with id '#{id}' and token '#{@current_token}'"
128
- beetle.publish(:pong, {"id" => id, "token" => @current_token}.to_json)
129
- end
130
-
131
- def client_started!
132
- logger.info "Sending client_started message with id '#{id}'"
133
- beetle.publish(:client_started, {"id" => id}.to_json)
134
- end
135
-
136
- def heartbeat!
137
- logger.info "Sending heartbeat message with id '#{id}'"
138
- beetle.publish(:heartbeat, {"id" => id}.to_json)
139
- end
140
-
141
- def invalidate!
142
- @current_master = nil
143
- clear_redis_master_file
144
- logger.info "Sending client_invalidated message with id '#{id}' and token '#{@current_token}'"
145
- beetle.publish(:client_invalidated, {"id" => id, "token" => @current_token}.to_json)
146
- end
147
-
148
-
149
- # Dispatches messages from the queue to methods in RedisConfigurationClient
150
- class MessageDispatcher < Beetle::Handler #:nodoc:
151
- cattr_accessor :configuration_client
152
- def process
153
- @@configuration_client.__send__(message.header.routing_key, ActiveSupport::JSON.decode(message.data))
154
- end
155
- end
156
- end
157
- end
@@ -1,152 +0,0 @@
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
- response.headers['Refresh'] = '3; url=/'
28
- # headers = @http_headers.split("\0").inject({}){|h, s| (s =~ /^([^:]+): (.*)$/ && (h[$1] = $2)); h }
29
-
30
- case @http_request_uri
31
- when '/', '/.html'
32
- response.content_type 'text/html'
33
- server_status(response, "html")
34
- when "/.json"
35
- response.content_type 'application/json'
36
- server_status(response, "json")
37
- when "/.txt"
38
- response.content_type 'text/plain'
39
- server_status(response, "plain")
40
- when '/initiate_master_switch'
41
- initiate_master_switch(response)
42
- when '/brokers'
43
- list_brokers(response)
44
- else
45
- not_found(response)
46
- end
47
- response.send_response
48
- end
49
-
50
- def list_brokers(response)
51
- brokers = config_server.config.brokers
52
- response.status = 200
53
- if @http_headers =~ %r(application/json)
54
- response.content_type 'application/json'
55
- response.content = brokers.to_json
56
- else
57
- response.content_type 'text/yaml'
58
- response.content = brokers.to_yaml
59
- end
60
- end
61
-
62
- def server_status(response, type)
63
- response.status = 200
64
- status = config_server.status
65
- response.content =
66
- case type
67
- when "plain"
68
- plain_text_response(status)
69
- when "json"
70
- status.to_json
71
- when "html"
72
- html_response(status)
73
- end
74
- end
75
-
76
- def plain_text_response(status)
77
- status.keys.sort_by{|k| k.to_s}.reverse.map do |k|
78
- name = k.to_s # .split('_').join(" ")
79
- if (value = status[k]).is_a?(Array)
80
- value = value.empty? ? "none" : value.join(", ")
81
- end
82
- "#{name}: #{value}"
83
- end.join("\n")
84
- end
85
-
86
- def html_response(status)
87
- b = "<!doctype html>"
88
- b << "<html><head><title>Beetle Configuration Server Status</title>#{html_styles(status)}</head>"
89
- b << "<body><h1>Beetle Configuration Server Status</h1>"
90
- unless status[:redis_master_available?]
91
- b << "<form name='masterswitch' method='post' action='/initiate_master_switch'>"
92
- b << "Master down! "
93
- b << "<a href='javascript: document.masterswitch.submit();'>Initiate master switch</a> "
94
- b << "or wait until system performs it automatically."
95
- b << "</form>"
96
- end
97
- b << "<table cellspacing=0>\n"
98
- plain_text_response(status).split("\n").compact.each do |row|
99
- row =~/(^[^:]+): (.*)$/
100
- name, value = $1, $2
101
- if value =~ /,/
102
- value = "<ul>" << value.split(/\s*,\s*/).map{|s| "<li>#{s}</li>"}.join << "</ul>"
103
- end
104
- b << "<tr><td>#{name}</td><td>#{value}</td></tr>\n"
105
- end
106
- b << "</table>"
107
- b << "</body></html>"
108
- end
109
-
110
- def html_styles(status)
111
- warn_color = status[:redis_master_available?] ? "#5780b2" : "#A52A2A"
112
- <<"EOS"
113
- <style media="screen" type="text/css">
114
- html { font: 1.25em/1.5 arial, sans-serif;}
115
- body { margin: 1em; }
116
- table tr:nth-child(2n+1){ background-color: #ffffff; }
117
- td { padding: 0.1em 0.2em; vertical-align: top; }
118
- ul { list-style-type: none; margin: 0; padding: 0;}
119
- li { }
120
- h1 { color: #{warn_color}; margin-bottom: 0.2em;}
121
- a:link, a:visited {text-decoration:none; color:#A52A2A;}
122
- a:hover, a:active {text-decoration:none; color:#FF0000;}
123
- a {
124
- padding: 10px; background: #cdcdcd;
125
- -moz-border-radius: 5px;
126
- border-radius: 5px;
127
- -moz-box-shadow: 2px 2px 2px #bbb;
128
- -webkit-box-shadow: 2px 2px 2px #bbb;
129
- box-shadow: 2px 2px 2px #bbb;
130
- }
131
- form { font-size: 1em; margin-bottom: 1em; }
132
- </style>
133
- EOS
134
- end
135
-
136
- def initiate_master_switch(response)
137
- response.content_type 'text/plain'
138
- if config_server.initiate_master_switch
139
- response.status = 201
140
- response.content = "Master switch initiated"
141
- else
142
- response.status = 200
143
- response.content = "No master switch necessary"
144
- end
145
- end
146
-
147
- def not_found(response)
148
- response.content_type 'text/plain'
149
- response.status = 404
150
- end
151
- end
152
- end