anycable 0.5.2 → 0.6.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +166 -2
- data/MIT-LICENSE +1 -1
- data/README.md +19 -60
- data/bin/anycable +13 -0
- data/bin/anycabled +30 -0
- data/lib/anycable.rb +69 -18
- data/lib/anycable/broadcast_adapters.rb +33 -0
- data/lib/anycable/broadcast_adapters/redis.rb +42 -0
- data/lib/anycable/cli.rb +329 -0
- data/lib/anycable/config.rb +93 -17
- data/lib/anycable/exceptions_handling.rb +37 -0
- data/lib/anycable/health_server.rb +52 -31
- data/lib/anycable/middleware.rb +19 -0
- data/lib/anycable/middleware_chain.rb +58 -0
- data/lib/anycable/rpc/rpc_pb.rb +1 -1
- data/lib/anycable/rpc/rpc_services_pb.rb +1 -1
- data/lib/anycable/rpc_handler.rb +77 -32
- data/lib/anycable/server.rb +132 -39
- data/lib/anycable/socket.rb +1 -1
- data/lib/anycable/version.rb +2 -2
- metadata +34 -59
- data/.gitignore +0 -40
- data/.hound.yml +0 -3
- data/.rubocop.yml +0 -71
- data/.travis.yml +0 -13
- data/Gemfile +0 -8
- data/Makefile +0 -5
- data/PITCHME.md +0 -139
- data/PITCHME.yaml +0 -1
- data/Rakefile +0 -8
- data/anycable.gemspec +0 -32
- data/assets/Memory3.png +0 -0
- data/assets/Memory5.png +0 -0
- data/assets/RTT3.png +0 -0
- data/assets/RTT5.png +0 -0
- data/assets/Scheme1.png +0 -0
- data/assets/Scheme2.png +0 -0
- data/assets/cpu_chart.gif +0 -0
- data/assets/cpu_chart2.gif +0 -0
- data/assets/evlms.png +0 -0
- data/benchmarks/.gitignore +0 -1
- data/benchmarks/2017-02-12.md +0 -308
- data/benchmarks/2018-03-04.md +0 -192
- data/benchmarks/2018-05-27-rpc-bench.md +0 -57
- data/benchmarks/HowTo.md +0 -23
- data/benchmarks/ansible.cfg +0 -9
- data/benchmarks/benchmark.yml +0 -67
- data/benchmarks/hosts +0 -5
- data/benchmarks/servers.yml +0 -36
- data/circle.yml +0 -8
- data/etc/bug_report_template.rb +0 -76
- data/lib/anycable/handler/exceptions_handling.rb +0 -43
- data/lib/anycable/pubsub.rb +0 -26
- data/protos/rpc.proto +0 -55
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyCable
|
4
|
+
module BroadcastAdapters # :nodoc:
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# rubocop: disable Metrics/AbcSize, Metrics/MethodLength
|
8
|
+
def lookup_adapter(args)
|
9
|
+
adapter, options = Array(args)
|
10
|
+
path_to_adapter = "anycable/broadcast_adapters/#{adapter}"
|
11
|
+
adapter_class_name = adapter.to_s.split("_").map(&:capitalize).join
|
12
|
+
|
13
|
+
unless BroadcastAdapters.const_defined?(adapter_class_name, false)
|
14
|
+
begin
|
15
|
+
require path_to_adapter
|
16
|
+
rescue LoadError => e
|
17
|
+
# We couldn't require the adapter itself.
|
18
|
+
if e.path == path_to_adapter
|
19
|
+
raise e.class, "Couldn't load the '#{adapter}' broadcast adapter for AnyCable",
|
20
|
+
e.backtrace
|
21
|
+
# Bubbled up from the adapter require.
|
22
|
+
else
|
23
|
+
raise e.class, "Error loading the '#{adapter}' broadcast adapter for AnyCable",
|
24
|
+
e.backtrace
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
BroadcastAdapters.const_get(adapter_class_name, false).new(**(options || {}))
|
30
|
+
end
|
31
|
+
# rubocop: enable Metrics/AbcSize, Metrics/MethodLength
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem "redis", ">= 3"
|
4
|
+
|
5
|
+
require "redis"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module AnyCable
|
9
|
+
module BroadcastAdapters
|
10
|
+
# Redis adapter for broadcasting.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# AnyCable.broadast_adapter = :redis
|
15
|
+
#
|
16
|
+
# It uses Redis configuration from global AnyCable config
|
17
|
+
# by default.
|
18
|
+
#
|
19
|
+
# You can override these params:
|
20
|
+
#
|
21
|
+
# AnyCable.broadcast_adapter = :redis, url: "redis://my_redis", channel: "_any_cable_"
|
22
|
+
class Redis
|
23
|
+
attr_reader :redis_conn, :channel
|
24
|
+
|
25
|
+
def initialize(
|
26
|
+
channel: AnyCable.config.redis_channel,
|
27
|
+
**options
|
28
|
+
)
|
29
|
+
options = AnyCable.config.to_redis_params.merge(options)
|
30
|
+
@redis_conn = ::Redis.new(options)
|
31
|
+
@channel = channel
|
32
|
+
end
|
33
|
+
|
34
|
+
def broadcast(stream, payload)
|
35
|
+
redis_conn.publish(
|
36
|
+
channel,
|
37
|
+
{ stream: stream, data: payload }.to_json
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/anycable/cli.rb
ADDED
@@ -0,0 +1,329 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
require "anycable"
|
6
|
+
|
7
|
+
$stdout.sync = true
|
8
|
+
|
9
|
+
module AnyCable
|
10
|
+
# Command-line interface for running AnyCable gRPC server
|
11
|
+
# rubocop:disable Metrics/ClassLength
|
12
|
+
class CLI
|
13
|
+
# (not-so-big) List of common boot files for
|
14
|
+
# different applications
|
15
|
+
APP_CANDIDATES = %w[
|
16
|
+
./config/anycable.rb
|
17
|
+
./config/environment.rb
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
# Wait for external process termination (s)
|
21
|
+
WAIT_PROCESS = 2
|
22
|
+
|
23
|
+
attr_reader :server, :health_server
|
24
|
+
|
25
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
26
|
+
def run(args = {})
|
27
|
+
@at_stop = []
|
28
|
+
|
29
|
+
extra_options = parse_cli_options!(args)
|
30
|
+
|
31
|
+
# Boot app first, 'cause it might change
|
32
|
+
# configuration, loggin settings, etc.
|
33
|
+
boot_app!
|
34
|
+
|
35
|
+
parse_gem_options!(extra_options)
|
36
|
+
|
37
|
+
configure_server!
|
38
|
+
|
39
|
+
logger.info "Starting AnyCable gRPC server (pid: #{Process.pid})"
|
40
|
+
|
41
|
+
print_versions!
|
42
|
+
|
43
|
+
logger.info "Serving #{defined?(::Rails) ? 'Rails ' : ''}application from #{boot_file}"
|
44
|
+
|
45
|
+
verify_connection_factory!
|
46
|
+
|
47
|
+
log_grpc! if config.log_grpc
|
48
|
+
|
49
|
+
log_errors!
|
50
|
+
|
51
|
+
@server = AnyCable::Server.new(
|
52
|
+
host: config.rpc_host,
|
53
|
+
**config.to_grpc_params,
|
54
|
+
interceptors: AnyCable.middleware.to_a
|
55
|
+
)
|
56
|
+
|
57
|
+
# Make sure middlewares are not adding after server has started
|
58
|
+
AnyCable.middleware.freeze
|
59
|
+
|
60
|
+
start_health_server! if config.http_health_port_provided?
|
61
|
+
start_pubsub!
|
62
|
+
|
63
|
+
server.start
|
64
|
+
|
65
|
+
run_custom_server_command! unless server_command.nil?
|
66
|
+
|
67
|
+
begin
|
68
|
+
wait_till_terminated
|
69
|
+
rescue Interrupt => e
|
70
|
+
logger.info "Stopping... #{e.message}"
|
71
|
+
|
72
|
+
shutdown
|
73
|
+
|
74
|
+
logger.info "Stopped. Good-bye!"
|
75
|
+
exit(0)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
79
|
+
|
80
|
+
def shutdown
|
81
|
+
at_stop.each(&:call)
|
82
|
+
server.stop
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
attr_reader :boot_file, :server_command
|
88
|
+
|
89
|
+
def config
|
90
|
+
AnyCable.config
|
91
|
+
end
|
92
|
+
|
93
|
+
def logger
|
94
|
+
AnyCable.logger
|
95
|
+
end
|
96
|
+
|
97
|
+
def at_stop
|
98
|
+
if block_given?
|
99
|
+
@at_stop << Proc.new
|
100
|
+
else
|
101
|
+
@at_stop
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def wait_till_terminated
|
106
|
+
self_read = setup_signals
|
107
|
+
|
108
|
+
while readable_io = IO.select([self_read]) # rubocop:disable Lint/AssignmentInCondition
|
109
|
+
signal = readable_io.first[0].gets.strip
|
110
|
+
raise Interrupt, "SIG#{signal} received"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def setup_signals
|
115
|
+
self_read, self_write = IO.pipe
|
116
|
+
|
117
|
+
%w[INT TERM].each do |signal|
|
118
|
+
trap signal do
|
119
|
+
self_write.puts signal
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
self_read
|
124
|
+
end
|
125
|
+
|
126
|
+
def print_versions!
|
127
|
+
logger.info "AnyCable version: #{AnyCable::VERSION}"
|
128
|
+
logger.info "gRPC version: #{GRPC::VERSION}"
|
129
|
+
end
|
130
|
+
|
131
|
+
# rubocop:disable Metrics/MethodLength
|
132
|
+
def boot_app!
|
133
|
+
@boot_file ||= try_detect_app
|
134
|
+
|
135
|
+
if boot_file.nil?
|
136
|
+
$stdout.puts(
|
137
|
+
"Couldn't find an application to load. " \
|
138
|
+
"Please specify the explicit path via -r option, e.g:" \
|
139
|
+
" anycable -r ./config/boot.rb or anycable -r /app/config/load_me.rb"
|
140
|
+
)
|
141
|
+
exit(1)
|
142
|
+
end
|
143
|
+
|
144
|
+
begin
|
145
|
+
require boot_file
|
146
|
+
rescue LoadError => e
|
147
|
+
$stdout.puts(
|
148
|
+
"Failed to load application: #{e.message}. " \
|
149
|
+
"Please specify the explicit path via -r option, e.g:" \
|
150
|
+
" anycable -r ./config/boot.rb or anycable -r /app/config/load_me.rb"
|
151
|
+
)
|
152
|
+
exit(1)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
# rubocop:enable Metrics/MethodLength
|
156
|
+
|
157
|
+
def try_detect_app
|
158
|
+
APP_CANDIDATES.detect { |path| File.exist?(path) }
|
159
|
+
end
|
160
|
+
|
161
|
+
def configure_server!
|
162
|
+
AnyCable.server_callbacks.each(&:call)
|
163
|
+
end
|
164
|
+
|
165
|
+
def start_health_server!
|
166
|
+
@health_server = AnyCable::HealthServer.new(
|
167
|
+
server,
|
168
|
+
**config.to_http_health_params
|
169
|
+
)
|
170
|
+
health_server.start
|
171
|
+
|
172
|
+
at_stop { health_server.stop }
|
173
|
+
end
|
174
|
+
|
175
|
+
def start_pubsub!
|
176
|
+
logger.info "Broadcasting Redis channel: #{config.redis_channel}"
|
177
|
+
end
|
178
|
+
|
179
|
+
# rubocop: disable Metrics/MethodLength, Metrics/AbcSize
|
180
|
+
def run_custom_server_command!
|
181
|
+
pid = nil
|
182
|
+
stopped = false
|
183
|
+
command_thread = Thread.new do
|
184
|
+
pid = Process.spawn(server_command)
|
185
|
+
logger.info "Started command: #{server_command} (pid: #{pid})"
|
186
|
+
|
187
|
+
Process.wait pid
|
188
|
+
pid = nil
|
189
|
+
raise Interrupt, "Server command exit unexpectedly" unless stopped
|
190
|
+
end
|
191
|
+
|
192
|
+
command_thread.abort_on_exception = true
|
193
|
+
|
194
|
+
at_stop do
|
195
|
+
stopped = true
|
196
|
+
next if pid.nil?
|
197
|
+
|
198
|
+
Process.kill("SIGTERM", pid)
|
199
|
+
|
200
|
+
logger.info "Wait till process #{pid} stop..."
|
201
|
+
|
202
|
+
tick = 0
|
203
|
+
|
204
|
+
loop do
|
205
|
+
tick += 0.2
|
206
|
+
break if tick > WAIT_PROCESS
|
207
|
+
|
208
|
+
if pid.nil?
|
209
|
+
logger.info "Process #{pid} stopped."
|
210
|
+
break
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# rubocop: enable Metrics/MethodLength, Metrics/AbcSize
|
216
|
+
|
217
|
+
def log_grpc!
|
218
|
+
::GRPC.define_singleton_method(:logger) { AnyCable.logger }
|
219
|
+
end
|
220
|
+
|
221
|
+
# Add default exceptions handler: print error message to log
|
222
|
+
def log_errors!
|
223
|
+
if AnyCable.config.debug?
|
224
|
+
# Print error with backtrace in debug mode
|
225
|
+
AnyCable.capture_exception do |e|
|
226
|
+
AnyCable.logger.error("#{e.message}:\n#{e.backtrace.take(20).join("\n")}")
|
227
|
+
end
|
228
|
+
else
|
229
|
+
AnyCable.capture_exception { |e| AnyCable.logger.error(e.message) }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def verify_connection_factory!
|
234
|
+
return if AnyCable.connection_factory
|
235
|
+
|
236
|
+
logger.error "AnyCable connection factory must be configured. " \
|
237
|
+
"Make sure you've required a gem (e.g. `anycable-rails`) or " \
|
238
|
+
"configured `AnyCable.connection_factory` yourself"
|
239
|
+
exit(1)
|
240
|
+
end
|
241
|
+
|
242
|
+
def parse_gem_options!(args)
|
243
|
+
config.parse_options!(args)
|
244
|
+
rescue OptionParser::InvalidOption => e
|
245
|
+
$stdout.puts e.message
|
246
|
+
$stdout.puts "Run anycable -h to see available options"
|
247
|
+
exit(1)
|
248
|
+
end
|
249
|
+
|
250
|
+
# rubocop:disable Metrics/MethodLength
|
251
|
+
def parse_cli_options!(args)
|
252
|
+
unknown_opts = []
|
253
|
+
|
254
|
+
parser = build_cli_parser
|
255
|
+
|
256
|
+
begin
|
257
|
+
parser.parse!(args)
|
258
|
+
rescue OptionParser::InvalidOption => e
|
259
|
+
unknown_opts << e.args[0]
|
260
|
+
unless args.size.zero?
|
261
|
+
unknown_opts << args.shift unless args.first.start_with?("-")
|
262
|
+
retry
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
unknown_opts
|
267
|
+
end
|
268
|
+
|
269
|
+
def build_cli_parser
|
270
|
+
OptionParser.new do |o|
|
271
|
+
o.on "-v", "--version", "Print version and exit" do |_arg|
|
272
|
+
$stdout.puts "AnyCable v#{AnyCable::VERSION}"
|
273
|
+
exit(0)
|
274
|
+
end
|
275
|
+
|
276
|
+
o.on "-r", "--require [PATH|DIR]", "Location of application file to require" do |arg|
|
277
|
+
@boot_file = arg
|
278
|
+
end
|
279
|
+
|
280
|
+
o.on "--server-command VALUE", "Command to run WebSocket server" do |arg|
|
281
|
+
@server_command = arg
|
282
|
+
end
|
283
|
+
|
284
|
+
o.on_tail "-h", "--help", "Show help" do
|
285
|
+
$stdout.puts usage
|
286
|
+
exit(0)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
# rubocop:enable Metrics/MethodLength
|
291
|
+
|
292
|
+
def usage
|
293
|
+
<<~HELP
|
294
|
+
anycable: run AnyCable gRPC server (https://anycable.io)
|
295
|
+
|
296
|
+
VERSION
|
297
|
+
anycable/#{AnyCable::VERSION}
|
298
|
+
|
299
|
+
USAGE
|
300
|
+
$ anycable [options]
|
301
|
+
|
302
|
+
BASIC OPTIONS
|
303
|
+
-r, --require=path Location of application file to require, default: "config/environment.rb"
|
304
|
+
--server-command=command Command to run WebSocket server
|
305
|
+
--rpc-host=host Local address to run gRPC server on, default: "[::]:50051"
|
306
|
+
--redis-url=url Redis URL for pub/sub, default: REDIS_URL or "redis://localhost:6379/5"
|
307
|
+
--redis-channel=name Redis channel for broadcasting, default: "__anycable__"
|
308
|
+
--redis-sentinels=<...hosts> Redis Sentinel followers addresses (as a comma-separated list), default: nil
|
309
|
+
--log-level=level Logging level, default: "info"
|
310
|
+
--log-file=path Path to log file, default: <none> (log to STDOUT)
|
311
|
+
--log-grpc Enable gRPC logging (disabled by default)
|
312
|
+
--debug Turn on verbose logging ("debug" level and gRPC logging on)
|
313
|
+
-v, --version Print version and exit
|
314
|
+
-h, --help Show this help
|
315
|
+
|
316
|
+
HTTP HEALTH CHECKER OPTIONS
|
317
|
+
--http-health-port=port Port to run HTTP health server on, default: <none> (disabled)
|
318
|
+
--http-health-path=path Endpoint to server health cheks, default: "/health"
|
319
|
+
|
320
|
+
GRPC OPTIONS
|
321
|
+
--rpc-pool-size=size gRPC workers pool size, default: 30
|
322
|
+
--rpc-max-waiting-requests=num Max waiting requests queue size, default: 20
|
323
|
+
--rpc-poll-period=seconds Poll period (sec), default: 1
|
324
|
+
--rpc-pool-keep-alive=seconds Keep-alive polling interval (sec), default: 1
|
325
|
+
HELP
|
326
|
+
end
|
327
|
+
end
|
328
|
+
# rubocop:enable Metrics/ClassLength
|
329
|
+
end
|
data/lib/anycable/config.rb
CHANGED
@@ -1,32 +1,108 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "anyway_config"
|
4
|
+
require "grpc"
|
4
5
|
|
5
|
-
module
|
6
|
-
#
|
6
|
+
module AnyCable
|
7
|
+
# AnyCable configuration.
|
7
8
|
class Config < Anyway::Config
|
8
9
|
config_name :anycable
|
9
10
|
|
10
|
-
|
11
|
-
redis_url: "redis://localhost:6379/5",
|
12
|
-
redis_sentinels: [],
|
13
|
-
redis_channel: "__anycable__",
|
14
|
-
log_file: nil,
|
15
|
-
log_level: :info,
|
16
|
-
log_grpc: false,
|
17
|
-
debug: false, # Shortcut to enable GRPC logging and debug level
|
18
|
-
http_health_port: nil
|
11
|
+
DefaultHostWrapper = Class.new(String)
|
19
12
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
13
|
+
attr_config(
|
14
|
+
### gRPC options
|
15
|
+
rpc_host: DefaultHostWrapper.new("[::]:50051"),
|
16
|
+
# For defaults see https://github.com/grpc/grpc/blob/51f0d35509bcdaba572d422c4f856208162022de/src/ruby/lib/grpc/generic/rpc_server.rb#L186-L216
|
17
|
+
rpc_pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
|
18
|
+
rpc_max_waiting_requests: GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
|
19
|
+
rpc_poll_period: GRPC::RpcServer::DEFAULT_POLL_PERIOD,
|
20
|
+
rpc_pool_keep_alive: GRPC::Pool::DEFAULT_KEEP_ALIVE,
|
21
|
+
# See https://github.com/grpc/grpc/blob/f526602bff029b8db50a8d57134d72da33d8a752/include/grpc/impl/codegen/grpc_types.h#L292-L315
|
22
|
+
rpc_server_args: {},
|
23
|
+
|
24
|
+
### Redis options
|
25
|
+
redis_url: ENV.fetch("REDIS_URL", "redis://localhost:6379/5"),
|
26
|
+
redis_sentinels: nil,
|
27
|
+
redis_channel: "__anycable__",
|
28
|
+
|
29
|
+
### Logging options
|
30
|
+
log_file: nil,
|
31
|
+
log_level: :info,
|
32
|
+
log_grpc: false,
|
33
|
+
debug: false, # Shortcut to enable GRPC logging and debug level
|
34
|
+
|
35
|
+
### Health check options
|
36
|
+
http_health_port: nil,
|
37
|
+
http_health_path: "/health"
|
38
|
+
)
|
39
|
+
|
40
|
+
ignore_options :rpc_server_args
|
41
|
+
flag_options :log_grpc, :debug
|
42
|
+
|
43
|
+
def log_level
|
44
|
+
debug ? :debug : @log_level
|
45
|
+
end
|
46
|
+
|
47
|
+
def log_grpc
|
48
|
+
debug || @log_grpc
|
26
49
|
end
|
27
50
|
|
51
|
+
def debug
|
52
|
+
@debug != false
|
53
|
+
end
|
54
|
+
|
55
|
+
alias debug? debug
|
56
|
+
|
28
57
|
def http_health_port_provided?
|
29
58
|
!http_health_port.nil? && http_health_port != ""
|
30
59
|
end
|
60
|
+
|
61
|
+
# Build gRPC server parameters
|
62
|
+
def to_grpc_params
|
63
|
+
{
|
64
|
+
pool_size: rpc_pool_size,
|
65
|
+
max_waiting_requests: rpc_max_waiting_requests,
|
66
|
+
poll_period: rpc_poll_period,
|
67
|
+
pool_keep_alive: rpc_pool_keep_alive,
|
68
|
+
server_args: rpc_server_args
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Build Redis parameters
|
73
|
+
def to_redis_params
|
74
|
+
{ url: redis_url }.tap do |params|
|
75
|
+
next if redis_sentinels.nil?
|
76
|
+
|
77
|
+
raise ArgumentError, "redis_sentinels must be an array; got #{redis_sentinels}" unless
|
78
|
+
redis_sentinels.is_a?(Array)
|
79
|
+
|
80
|
+
next if redis_sentinels.empty?
|
81
|
+
|
82
|
+
params[:sentinels] = redis_sentinels.map(&method(:parse_sentinel))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Build HTTP health server parameters
|
87
|
+
def to_http_health_params
|
88
|
+
{
|
89
|
+
port: http_health_port,
|
90
|
+
path: http_health_path
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
SENTINEL_RXP = /^([\w\-_]*)\:(\d+)$/.freeze
|
97
|
+
|
98
|
+
def parse_sentinel(sentinel)
|
99
|
+
return sentinel if sentinel.is_a?(Hash)
|
100
|
+
|
101
|
+
matches = sentinel.match(SENTINEL_RXP)
|
102
|
+
|
103
|
+
raise ArgumentError, "Invalid Sentinel value: #{sentinel}" if matches.nil?
|
104
|
+
|
105
|
+
{ "host" => matches[1], "port" => matches[2].to_i }
|
106
|
+
end
|
31
107
|
end
|
32
108
|
end
|