protobuffy 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/CHANGES.md +261 -0
- data/CONTRIBUTING.md +16 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +14 -0
- data/README.md +58 -0
- data/Rakefile +61 -0
- data/bin/protoc-gen-ruby +17 -0
- data/bin/rpc_server +4 -0
- data/examples/bin/reverse-client-http +4 -0
- data/examples/bin/reverse-client-socket +4 -0
- data/examples/bin/reverse-client-zmq +4 -0
- data/examples/config.ru +6 -0
- data/examples/definitions/example/reverse.proto +12 -0
- data/examples/lib/example/reverse-client.rb +23 -0
- data/examples/lib/example/reverse-service.rb +9 -0
- data/examples/lib/example/reverse.pb.rb +36 -0
- data/lib/protobuf.rb +106 -0
- data/lib/protobuf/cli.rb +249 -0
- data/lib/protobuf/code_generator.rb +41 -0
- data/lib/protobuf/decoder.rb +74 -0
- data/lib/protobuf/deprecator.rb +42 -0
- data/lib/protobuf/descriptors.rb +3 -0
- data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +52 -0
- data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +249 -0
- data/lib/protobuf/encoder.rb +62 -0
- data/lib/protobuf/enum.rb +319 -0
- data/lib/protobuf/exceptions.rb +9 -0
- data/lib/protobuf/field.rb +74 -0
- data/lib/protobuf/field/base_field.rb +280 -0
- data/lib/protobuf/field/bool_field.rb +53 -0
- data/lib/protobuf/field/bytes_field.rb +81 -0
- data/lib/protobuf/field/double_field.rb +26 -0
- data/lib/protobuf/field/enum_field.rb +57 -0
- data/lib/protobuf/field/field_array.rb +86 -0
- data/lib/protobuf/field/fixed32_field.rb +25 -0
- data/lib/protobuf/field/fixed64_field.rb +29 -0
- data/lib/protobuf/field/float_field.rb +38 -0
- data/lib/protobuf/field/int32_field.rb +22 -0
- data/lib/protobuf/field/int64_field.rb +22 -0
- data/lib/protobuf/field/integer_field.rb +24 -0
- data/lib/protobuf/field/message_field.rb +66 -0
- data/lib/protobuf/field/sfixed32_field.rb +28 -0
- data/lib/protobuf/field/sfixed64_field.rb +29 -0
- data/lib/protobuf/field/signed_integer_field.rb +30 -0
- data/lib/protobuf/field/sint32_field.rb +22 -0
- data/lib/protobuf/field/sint64_field.rb +22 -0
- data/lib/protobuf/field/string_field.rb +35 -0
- data/lib/protobuf/field/uint32_field.rb +22 -0
- data/lib/protobuf/field/uint64_field.rb +22 -0
- data/lib/protobuf/field/varint_field.rb +68 -0
- data/lib/protobuf/generators/base.rb +71 -0
- data/lib/protobuf/generators/enum_generator.rb +42 -0
- data/lib/protobuf/generators/extension_generator.rb +28 -0
- data/lib/protobuf/generators/field_generator.rb +132 -0
- data/lib/protobuf/generators/file_generator.rb +140 -0
- data/lib/protobuf/generators/group_generator.rb +113 -0
- data/lib/protobuf/generators/message_generator.rb +99 -0
- data/lib/protobuf/generators/printable.rb +161 -0
- data/lib/protobuf/generators/service_generator.rb +27 -0
- data/lib/protobuf/http.rb +20 -0
- data/lib/protobuf/lifecycle.rb +46 -0
- data/lib/protobuf/logger.rb +86 -0
- data/lib/protobuf/message.rb +182 -0
- data/lib/protobuf/message/fields.rb +122 -0
- data/lib/protobuf/message/serialization.rb +84 -0
- data/lib/protobuf/optionable.rb +23 -0
- data/lib/protobuf/rpc/buffer.rb +79 -0
- data/lib/protobuf/rpc/client.rb +168 -0
- data/lib/protobuf/rpc/connector.rb +21 -0
- data/lib/protobuf/rpc/connectors/base.rb +54 -0
- data/lib/protobuf/rpc/connectors/common.rb +172 -0
- data/lib/protobuf/rpc/connectors/http.rb +90 -0
- data/lib/protobuf/rpc/connectors/socket.rb +73 -0
- data/lib/protobuf/rpc/connectors/zmq.rb +205 -0
- data/lib/protobuf/rpc/dynamic_discovery.pb.rb +47 -0
- data/lib/protobuf/rpc/env.rb +58 -0
- data/lib/protobuf/rpc/error.rb +28 -0
- data/lib/protobuf/rpc/error/client_error.rb +31 -0
- data/lib/protobuf/rpc/error/server_error.rb +43 -0
- data/lib/protobuf/rpc/middleware.rb +25 -0
- data/lib/protobuf/rpc/middleware/exception_handler.rb +36 -0
- data/lib/protobuf/rpc/middleware/logger.rb +91 -0
- data/lib/protobuf/rpc/middleware/request_decoder.rb +83 -0
- data/lib/protobuf/rpc/middleware/response_encoder.rb +88 -0
- data/lib/protobuf/rpc/middleware/runner.rb +18 -0
- data/lib/protobuf/rpc/rpc.pb.rb +53 -0
- data/lib/protobuf/rpc/server.rb +39 -0
- data/lib/protobuf/rpc/servers/http/server.rb +101 -0
- data/lib/protobuf/rpc/servers/http_runner.rb +34 -0
- data/lib/protobuf/rpc/servers/socket/server.rb +113 -0
- data/lib/protobuf/rpc/servers/socket/worker.rb +56 -0
- data/lib/protobuf/rpc/servers/socket_runner.rb +34 -0
- data/lib/protobuf/rpc/servers/zmq/broker.rb +155 -0
- data/lib/protobuf/rpc/servers/zmq/server.rb +313 -0
- data/lib/protobuf/rpc/servers/zmq/util.rb +47 -0
- data/lib/protobuf/rpc/servers/zmq/worker.rb +105 -0
- data/lib/protobuf/rpc/servers/zmq_runner.rb +51 -0
- data/lib/protobuf/rpc/service.rb +179 -0
- data/lib/protobuf/rpc/service_directory.rb +245 -0
- data/lib/protobuf/rpc/service_dispatcher.rb +46 -0
- data/lib/protobuf/rpc/service_filters.rb +273 -0
- data/lib/protobuf/rpc/stat.rb +148 -0
- data/lib/protobuf/socket.rb +22 -0
- data/lib/protobuf/tasks.rb +1 -0
- data/lib/protobuf/tasks/compile.rake +61 -0
- data/lib/protobuf/version.rb +3 -0
- data/lib/protobuf/wire_type.rb +10 -0
- data/lib/protobuf/zmq.rb +21 -0
- data/proto/dynamic_discovery.proto +44 -0
- data/proto/google/protobuf/compiler/plugin.proto +147 -0
- data/proto/google/protobuf/descriptor.proto +620 -0
- data/proto/rpc.proto +62 -0
- data/protobuffy.gemspec +37 -0
- data/spec/benchmark/tasks.rb +113 -0
- data/spec/bin/protoc-gen-ruby_spec.rb +18 -0
- data/spec/data/data.bin +3 -0
- data/spec/data/types.bin +0 -0
- data/spec/encoding/all_types_spec.rb +91 -0
- data/spec/encoding/extreme_values_spec.rb +0 -0
- data/spec/functional/socket_server_spec.rb +59 -0
- data/spec/functional/zmq_server_spec.rb +103 -0
- data/spec/lib/protobuf/cli_spec.rb +267 -0
- data/spec/lib/protobuf/code_generator_spec.rb +60 -0
- data/spec/lib/protobuf/enum_spec.rb +239 -0
- data/spec/lib/protobuf/field/int32_field_spec.rb +7 -0
- data/spec/lib/protobuf/field/string_field_spec.rb +46 -0
- data/spec/lib/protobuf/field_spec.rb +194 -0
- data/spec/lib/protobuf/generators/base_spec.rb +87 -0
- data/spec/lib/protobuf/generators/enum_generator_spec.rb +68 -0
- data/spec/lib/protobuf/generators/extension_generator_spec.rb +43 -0
- data/spec/lib/protobuf/generators/field_generator_spec.rb +99 -0
- data/spec/lib/protobuf/generators/file_generator_spec.rb +29 -0
- data/spec/lib/protobuf/generators/message_generator_spec.rb +0 -0
- data/spec/lib/protobuf/generators/service_generator_spec.rb +43 -0
- data/spec/lib/protobuf/lifecycle_spec.rb +89 -0
- data/spec/lib/protobuf/logger_spec.rb +136 -0
- data/spec/lib/protobuf/message_spec.rb +368 -0
- data/spec/lib/protobuf/optionable_spec.rb +46 -0
- data/spec/lib/protobuf/rpc/client_spec.rb +66 -0
- data/spec/lib/protobuf/rpc/connector_spec.rb +26 -0
- data/spec/lib/protobuf/rpc/connectors/base_spec.rb +50 -0
- data/spec/lib/protobuf/rpc/connectors/common_spec.rb +170 -0
- data/spec/lib/protobuf/rpc/connectors/connector_spec.rb +13 -0
- data/spec/lib/protobuf/rpc/connectors/http_spec.rb +61 -0
- data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +24 -0
- data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +129 -0
- data/spec/lib/protobuf/rpc/middleware/exception_handler_spec.rb +62 -0
- data/spec/lib/protobuf/rpc/middleware/logger_spec.rb +49 -0
- data/spec/lib/protobuf/rpc/middleware/request_decoder_spec.rb +115 -0
- data/spec/lib/protobuf/rpc/middleware/response_encoder_spec.rb +75 -0
- data/spec/lib/protobuf/rpc/servers/http/server_spec.rb +104 -0
- data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +38 -0
- data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +41 -0
- data/spec/lib/protobuf/rpc/servers/zmq/util_spec.rb +55 -0
- data/spec/lib/protobuf/rpc/servers/zmq/worker_spec.rb +35 -0
- data/spec/lib/protobuf/rpc/service_directory_spec.rb +295 -0
- data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +52 -0
- data/spec/lib/protobuf/rpc/service_filters_spec.rb +484 -0
- data/spec/lib/protobuf/rpc/service_spec.rb +161 -0
- data/spec/lib/protobuf/rpc/stat_spec.rb +151 -0
- data/spec/lib/protobuf_spec.rb +78 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/support/all.rb +7 -0
- data/spec/support/packed_field.rb +22 -0
- data/spec/support/server.rb +94 -0
- data/spec/support/test/all_types.data.bin +0 -0
- data/spec/support/test/all_types.data.txt +119 -0
- data/spec/support/test/defaults.pb.rb +25 -0
- data/spec/support/test/defaults.proto +9 -0
- data/spec/support/test/enum.pb.rb +59 -0
- data/spec/support/test/enum.proto +34 -0
- data/spec/support/test/extended.pb.rb +22 -0
- data/spec/support/test/extended.proto +10 -0
- data/spec/support/test/extreme_values.data.bin +0 -0
- data/spec/support/test/google_unittest.pb.rb +543 -0
- data/spec/support/test/google_unittest.proto +713 -0
- data/spec/support/test/google_unittest_import.pb.rb +37 -0
- data/spec/support/test/google_unittest_import.proto +64 -0
- data/spec/support/test/google_unittest_import_public.pb.rb +8 -0
- data/spec/support/test/google_unittest_import_public.proto +38 -0
- data/spec/support/test/multi_field_extensions.pb.rb +56 -0
- data/spec/support/test/multi_field_extensions.proto +33 -0
- data/spec/support/test/resource.pb.rb +117 -0
- data/spec/support/test/resource.proto +94 -0
- data/spec/support/test/resource_service.rb +26 -0
- data/spec/support/test_app_file.rb +2 -0
- data/spec/support/tolerance_matcher.rb +40 -0
- metadata +367 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Protobuf
|
4
|
+
module Rpc
|
5
|
+
class ZmqRunner
|
6
|
+
include ::Protobuf::Logger::LogMethods
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = case
|
10
|
+
when options.is_a?(OpenStruct) then
|
11
|
+
options.marshal_dump
|
12
|
+
when options.respond_to?(:to_hash) then
|
13
|
+
options.to_hash
|
14
|
+
else
|
15
|
+
raise "Cannot parser Zmq Server - server options"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
@server = ::Protobuf::Rpc::Zmq::Server.new(@options)
|
22
|
+
register_signals
|
23
|
+
@server.run do
|
24
|
+
yield if block_given?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def running?
|
29
|
+
@server.try :running?
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop
|
33
|
+
@server.try :stop
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def register_signals
|
39
|
+
trap(:TTIN) do
|
40
|
+
@server.add_worker
|
41
|
+
log_info { "Increased worker size to: #{@server.total_workers}" }
|
42
|
+
end
|
43
|
+
|
44
|
+
trap(:TTOU) do
|
45
|
+
log_info { "Current worker size: #{@server.workers.size}" }
|
46
|
+
log_info { "Current worker size: #{@server.busy_worker_count}" }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'protobuf/logger'
|
2
|
+
require 'protobuf/rpc/client'
|
3
|
+
require 'protobuf/rpc/error'
|
4
|
+
require 'protobuf/rpc/service_filters'
|
5
|
+
|
6
|
+
module Protobuf
|
7
|
+
module Rpc
|
8
|
+
# Object to encapsulate the request/response types for a given service method
|
9
|
+
#
|
10
|
+
RpcMethod = Struct.new("RpcMethod", :method, :request_type, :response_type)
|
11
|
+
|
12
|
+
class Service
|
13
|
+
include ::Protobuf::Logger::LogMethods
|
14
|
+
include ::Protobuf::Rpc::ServiceFilters
|
15
|
+
|
16
|
+
DEFAULT_HOST = '127.0.0.1'.freeze
|
17
|
+
DEFAULT_PORT = 9399
|
18
|
+
|
19
|
+
attr_reader :env, :request
|
20
|
+
|
21
|
+
##
|
22
|
+
# Constructor!
|
23
|
+
#
|
24
|
+
# Initialize a service with the rpc endpoint name and the bytes
|
25
|
+
# for the request.
|
26
|
+
def initialize(env)
|
27
|
+
@env = env.dup # Dup the env so it doesn't change out from under us
|
28
|
+
@request = env.request
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Class Methods
|
33
|
+
#
|
34
|
+
# Create a new client for the given service.
|
35
|
+
# See Client#initialize and ClientConnection::DEFAULT_OPTIONS
|
36
|
+
# for all available options.
|
37
|
+
#
|
38
|
+
def self.client(options = {})
|
39
|
+
::Protobuf::Rpc::Client.new({ :service => self,
|
40
|
+
:host => host,
|
41
|
+
:port => port }.merge(options))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Allows service-level configuration of location.
|
45
|
+
# Useful for system-startup configuration of a service
|
46
|
+
# so that any Clients using the Service.client sugar
|
47
|
+
# will not have to configure the location each time.
|
48
|
+
#
|
49
|
+
def self.configure(config = {})
|
50
|
+
self.host = config[:host] if config.key?(:host)
|
51
|
+
self.port = config[:port] if config.key?(:port)
|
52
|
+
end
|
53
|
+
|
54
|
+
# The host location of the service.
|
55
|
+
#
|
56
|
+
def self.host
|
57
|
+
@_host ||= DEFAULT_HOST
|
58
|
+
end
|
59
|
+
|
60
|
+
# The host location setter.
|
61
|
+
#
|
62
|
+
def self.host=(new_host)
|
63
|
+
@_host = new_host
|
64
|
+
end
|
65
|
+
|
66
|
+
# An array of defined service classes that contain implementation
|
67
|
+
# code
|
68
|
+
def self.implemented_services
|
69
|
+
classes = (self.subclasses || []).select do |subclass|
|
70
|
+
subclass.rpcs.any? do |(name, _)|
|
71
|
+
subclass.method_defined? name
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
classes.map(&:name)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Shorthand call to configure, passing a string formatted as hostname:port
|
79
|
+
# e.g. 127.0.0.1:9933
|
80
|
+
# e.g. localhost:0
|
81
|
+
#
|
82
|
+
def self.located_at(location)
|
83
|
+
return if location.nil? || location.downcase.strip !~ /.+:\d+/
|
84
|
+
host, port = location.downcase.strip.split ':'
|
85
|
+
configure(:host => host, :port => port.to_i)
|
86
|
+
end
|
87
|
+
|
88
|
+
# The port of the service on the destination server.
|
89
|
+
#
|
90
|
+
def self.port
|
91
|
+
@_port ||= DEFAULT_PORT
|
92
|
+
end
|
93
|
+
|
94
|
+
# The port location setter.
|
95
|
+
#
|
96
|
+
def self.port=(new_port)
|
97
|
+
@_port = new_port
|
98
|
+
end
|
99
|
+
|
100
|
+
# Define an rpc method with the given request and response types.
|
101
|
+
# This methods is only used by the generated service definitions
|
102
|
+
# and not useful for user code.
|
103
|
+
#
|
104
|
+
def self.rpc(method, request_type, response_type)
|
105
|
+
rpcs[method] = RpcMethod.new(method, request_type, response_type)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Hash containing the set of methods defined via `rpc`.
|
109
|
+
#
|
110
|
+
def self.rpcs
|
111
|
+
@_rpcs ||= {}
|
112
|
+
end
|
113
|
+
|
114
|
+
# Check if the given method name is a known rpc endpoint.
|
115
|
+
#
|
116
|
+
def self.rpc_method?(name)
|
117
|
+
rpcs.key?(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Instance Methods
|
122
|
+
#
|
123
|
+
# Get a callable object that will be used by the dispatcher
|
124
|
+
# to invoke the specified rpc method. Facilitates callback dispatch.
|
125
|
+
# The returned lambda is expected to be called at a later time (which
|
126
|
+
# is why we wrap the method call).
|
127
|
+
#
|
128
|
+
def callable_rpc_method(method_name)
|
129
|
+
lambda { run_filters(method_name) }
|
130
|
+
end
|
131
|
+
|
132
|
+
# Response object for this rpc cycle. Not assignable.
|
133
|
+
#
|
134
|
+
def response
|
135
|
+
@_response ||= response_type.new
|
136
|
+
end
|
137
|
+
|
138
|
+
# Convenience method to get back to class method.
|
139
|
+
#
|
140
|
+
def rpc_method?(name)
|
141
|
+
self.class.rpc_method?(name)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Convenience method to get back to class rpcs hash.
|
145
|
+
#
|
146
|
+
def rpcs
|
147
|
+
self.class.rpcs
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def request_type
|
153
|
+
@_request_type ||= env.request_type
|
154
|
+
end
|
155
|
+
|
156
|
+
# Sugar to make an rpc method feel like a controller method.
|
157
|
+
# If this method is not called, the response will be the memoized
|
158
|
+
# object returned by the response reader.
|
159
|
+
#
|
160
|
+
def respond_with(candidate)
|
161
|
+
@_response = candidate
|
162
|
+
end
|
163
|
+
alias_method :return_from_whence_you_came, :respond_with
|
164
|
+
|
165
|
+
def response_type
|
166
|
+
@_response_type ||= env.response_type
|
167
|
+
end
|
168
|
+
|
169
|
+
# Automatically fail a service method.
|
170
|
+
#
|
171
|
+
def rpc_failed(message)
|
172
|
+
message = message.message if message.respond_to?(:message)
|
173
|
+
raise RpcFailed.new(message)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
ActiveSupport.run_load_hooks(:protobuf_rpc_service, Service)
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'singleton'
|
3
|
+
require 'socket'
|
4
|
+
require 'set'
|
5
|
+
require 'thread'
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
require 'protobuf/rpc/dynamic_discovery.pb'
|
9
|
+
|
10
|
+
module Protobuf
|
11
|
+
module Rpc
|
12
|
+
class ServiceDirectory
|
13
|
+
include ::Singleton
|
14
|
+
include ::Protobuf::Logger::LogMethods
|
15
|
+
|
16
|
+
DEFAULT_ADDRESS = "0.0.0.0"
|
17
|
+
DEFAULT_PORT = 53000
|
18
|
+
DEFAULT_TIMEOUT = 1
|
19
|
+
|
20
|
+
class Listing < Delegator
|
21
|
+
attr_reader :expires_at
|
22
|
+
|
23
|
+
def initialize(server)
|
24
|
+
update(server)
|
25
|
+
end
|
26
|
+
|
27
|
+
def current?
|
28
|
+
!expired?
|
29
|
+
end
|
30
|
+
|
31
|
+
def eql?(other)
|
32
|
+
uuid.eql?(other.uuid)
|
33
|
+
end
|
34
|
+
|
35
|
+
def expired?
|
36
|
+
Time.now.to_i >= @expires_at
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash
|
40
|
+
uuid.hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def ttl
|
44
|
+
[super.to_i, 1].max
|
45
|
+
end
|
46
|
+
|
47
|
+
def update(server)
|
48
|
+
@server = server
|
49
|
+
@expires_at = Time.now.to_i + ttl
|
50
|
+
end
|
51
|
+
|
52
|
+
def __getobj__
|
53
|
+
@server
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Class Methods
|
58
|
+
#
|
59
|
+
class << self
|
60
|
+
attr_writer :address, :port
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.address
|
64
|
+
@address ||= DEFAULT_ADDRESS
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.port
|
68
|
+
@port ||= DEFAULT_PORT
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.start
|
72
|
+
yield(self) if block_given?
|
73
|
+
self.instance.start
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.stop
|
77
|
+
self.instance.stop
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Instance Methods
|
82
|
+
#
|
83
|
+
def initialize
|
84
|
+
reset
|
85
|
+
end
|
86
|
+
|
87
|
+
def all_listings_for(service)
|
88
|
+
if running? && @listings_by_service.key?(service.to_s)
|
89
|
+
@listings_by_service[service.to_s].entries.shuffle
|
90
|
+
else
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def each_listing(&block)
|
96
|
+
@listings_by_uuid.each_value(&block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def lookup(service)
|
100
|
+
if running?
|
101
|
+
if @listings_by_service.key?(service.to_s)
|
102
|
+
@listings_by_service[service.to_s].entries.sample
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def restart
|
108
|
+
stop
|
109
|
+
start
|
110
|
+
end
|
111
|
+
|
112
|
+
def running?
|
113
|
+
!!@thread.try(:alive?)
|
114
|
+
end
|
115
|
+
|
116
|
+
def start
|
117
|
+
unless running?
|
118
|
+
init_socket
|
119
|
+
log_info { sign_message("listening to udp://#{self.class.address}:#{self.class.port}") }
|
120
|
+
@thread = Thread.new { self.send(:run) }
|
121
|
+
end
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def stop
|
127
|
+
log_info { sign_message("Stopping directory") }
|
128
|
+
|
129
|
+
@thread.try(:kill).try(:join)
|
130
|
+
@socket.try(:close)
|
131
|
+
|
132
|
+
reset
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def add_or_update_listing(uuid, server)
|
138
|
+
listing = @listings_by_uuid[uuid]
|
139
|
+
|
140
|
+
if listing
|
141
|
+
action = :updated
|
142
|
+
listing.update(server)
|
143
|
+
else
|
144
|
+
action = :added
|
145
|
+
listing = Listing.new(server)
|
146
|
+
@listings_by_uuid[uuid] = listing
|
147
|
+
end
|
148
|
+
|
149
|
+
listing.services.each do |service|
|
150
|
+
@listings_by_service[service] << listing
|
151
|
+
end
|
152
|
+
|
153
|
+
trigger(action, listing)
|
154
|
+
log_debug { sign_message("#{action} server: #{server.inspect}") }
|
155
|
+
end
|
156
|
+
|
157
|
+
def init_socket
|
158
|
+
@socket = UDPSocket.new
|
159
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
|
160
|
+
|
161
|
+
if defined?(::Socket::SO_REUSEPORT)
|
162
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, true)
|
163
|
+
end
|
164
|
+
|
165
|
+
@socket.bind(self.class.address, self.class.port.to_i)
|
166
|
+
end
|
167
|
+
|
168
|
+
def process_beacon(beacon)
|
169
|
+
server = beacon.server
|
170
|
+
uuid = server.try(:uuid)
|
171
|
+
|
172
|
+
if server && uuid
|
173
|
+
case beacon.beacon_type
|
174
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::HEARTBEAT
|
175
|
+
add_or_update_listing(uuid, server)
|
176
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::FLATLINE
|
177
|
+
remove_listing(uuid)
|
178
|
+
end
|
179
|
+
else
|
180
|
+
log_info { sign_message("Ignoring incomplete beacon: #{beacon.inspect}") }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def read_beacon
|
185
|
+
data, addr = @socket.recvfrom(2048)
|
186
|
+
|
187
|
+
beacon = ::Protobuf::Rpc::DynamicDiscovery::Beacon.decode(data)
|
188
|
+
|
189
|
+
# Favor the address captured by the socket
|
190
|
+
beacon.try(:server).try(:address=, addr[3])
|
191
|
+
|
192
|
+
beacon
|
193
|
+
end
|
194
|
+
|
195
|
+
def remove_expired_listings
|
196
|
+
log_debug { sign_message("Removing expired listings") }
|
197
|
+
@listings_by_uuid.each do |uuid, listing|
|
198
|
+
remove_listing(uuid) if listing.expired?
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def remove_listing(uuid)
|
203
|
+
listing = @listings_by_uuid[uuid] or return
|
204
|
+
|
205
|
+
log_debug { sign_message("Removing listing: #{listing.inspect}") }
|
206
|
+
|
207
|
+
@listings_by_service.each do |service, listings|
|
208
|
+
listings.delete(listing)
|
209
|
+
end
|
210
|
+
|
211
|
+
trigger(:removed, @listings_by_uuid.delete(uuid))
|
212
|
+
end
|
213
|
+
|
214
|
+
def reset
|
215
|
+
@thread = nil
|
216
|
+
@socket = nil
|
217
|
+
@listings_by_uuid = {}
|
218
|
+
@listings_by_service = Hash.new { |h, k| h[k] = Set.new }
|
219
|
+
end
|
220
|
+
|
221
|
+
def run
|
222
|
+
sweep_interval = 1 # sweep expired listings every 1 second
|
223
|
+
next_sweep = Time.now.to_i + sweep_interval
|
224
|
+
|
225
|
+
loop do
|
226
|
+
timeout = [next_sweep - Time.now.to_i, 0.1].max
|
227
|
+
readable = IO.select([@socket], nil, nil, timeout)
|
228
|
+
process_beacon(read_beacon) if readable
|
229
|
+
|
230
|
+
if Time.now.to_i >= next_sweep
|
231
|
+
remove_expired_listings
|
232
|
+
next_sweep = Time.now.to_i + sweep_interval
|
233
|
+
end
|
234
|
+
end
|
235
|
+
rescue => e
|
236
|
+
log_debug { sign_message("ERROR: (#{e.class}) #{e.message}\n#{e.backtrace.join("\n")}") }
|
237
|
+
retry
|
238
|
+
end
|
239
|
+
|
240
|
+
def trigger(action, listing)
|
241
|
+
::ActiveSupport::Notifications.instrument("directory.listing.#{action}", :listing => listing)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|