ruby_skynet 0.1.2 → 0.2.0
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/Gemfile +3 -0
- data/Gemfile.lock +1 -1
- data/dev.log +0 -0
- data/lib/ruby_skynet/client.rb +32 -244
- data/lib/ruby_skynet/connection.rb +269 -0
- data/lib/ruby_skynet/doozer/client.rb +27 -20
- data/lib/ruby_skynet/exceptions.rb +2 -1
- data/lib/ruby_skynet/registry.rb +252 -0
- data/lib/ruby_skynet/version.rb +1 -1
- data/lib/ruby_skynet.rb +3 -1
- data/test/ruby_skynet_client_test.rb +36 -21
- data/test.log +0 -0
- metadata +6 -5
- data/ruby_skynet-0.1.0.gem +0 -0
- data/ruby_skynet-0.1.1.gem +0 -0
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/dev.log
ADDED
Binary file
|
data/lib/ruby_skynet/client.rb
CHANGED
@@ -1,7 +1,4 @@
|
|
1
1
|
require 'bson'
|
2
|
-
require 'sync_attr'
|
3
|
-
require 'multi_json'
|
4
|
-
|
5
2
|
#
|
6
3
|
# RubySkynet Client
|
7
4
|
#
|
@@ -13,124 +10,36 @@ module RubySkynet
|
|
13
10
|
class Client
|
14
11
|
include SyncAttr
|
15
12
|
|
16
|
-
# Default doozer configuration
|
17
|
-
# To replace this default, set the config as follows:
|
18
|
-
# RubySkynet::Client.doozer_config = { .... }
|
19
|
-
sync_attr_accessor :doozer_config do
|
20
|
-
{
|
21
|
-
:server => '127.0.0.1:8046',
|
22
|
-
:read_timeout => 5,
|
23
|
-
:connect_timeout => 3,
|
24
|
-
:connect_retry_interval => 0.1,
|
25
|
-
:connect_retry_count => 3
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
# Lazy initialize Doozer Client
|
30
|
-
sync_cattr_reader :doozer do
|
31
|
-
Doozer::Client.new
|
32
|
-
end
|
33
|
-
|
34
|
-
# Create a client connection, call the supplied block and close the connection on
|
35
|
-
# completion of the block
|
36
|
-
#
|
37
|
-
# Example
|
38
|
-
#
|
39
|
-
# require 'ruby_skynet'
|
40
|
-
# SemanticLogger.default_level = :trace
|
41
|
-
# SemanticLogger.appenders << SemanticLogger::Appender::File(STDOUT)
|
42
|
-
# RubySkynet::Client.connect('TutorialService') do |tutorial_service|
|
43
|
-
# p tutorial_service.call(:value => 5)
|
44
|
-
# end
|
45
|
-
def self.connect(service_name, params={})
|
46
|
-
begin
|
47
|
-
client = self.new(service_name, params)
|
48
|
-
yield(client)
|
49
|
-
ensure
|
50
|
-
client.close if client
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
13
|
# Returns a new RubySkynet Client for the named service
|
55
14
|
#
|
15
|
+
# Calls to an instance of the Client are thread-safe and can be called
|
16
|
+
# concurrently from multiple threads at the same time
|
17
|
+
#
|
56
18
|
# Parameters:
|
57
19
|
# :service_name
|
58
20
|
# Name of the service to look for and connect to on Skynet
|
59
21
|
#
|
60
|
-
# :
|
61
|
-
#
|
62
|
-
#
|
22
|
+
# :version
|
23
|
+
# Optional version number of the service in Skynet
|
24
|
+
# Default: '*' being the latest version of the service
|
63
25
|
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# server, only a connection failure or during an automatic reconnect
|
26
|
+
# :region
|
27
|
+
# Optional region for this service in Skynet
|
28
|
+
# Default: 'Development'
|
68
29
|
#
|
69
|
-
#
|
70
|
-
# Time in seconds to timeout on read
|
71
|
-
# Can be overridden by supplying a timeout in the read call
|
72
|
-
# Default: 60
|
73
|
-
#
|
74
|
-
# :connect_timeout [Float]
|
75
|
-
# Time in seconds to timeout when trying to connect to the server
|
76
|
-
# Default: Half of the :read_timeout ( 30 seconds )
|
30
|
+
# Example
|
77
31
|
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
32
|
+
# require 'ruby_skynet'
|
33
|
+
# SemanticLogger.default_level = :trace
|
34
|
+
# SemanticLogger.appenders << SemanticLogger::Appender::File(STDOUT)
|
81
35
|
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
|
85
|
-
def initialize(service_name, params = {})
|
36
|
+
# tutorial_service = RubySkynet::Client.new('TutorialService')
|
37
|
+
# p tutorial_service.call('Add', :value => 5)
|
38
|
+
def initialize(service_name, version='*', region='Development')
|
86
39
|
@service_name = service_name
|
87
|
-
@logger
|
88
|
-
|
89
|
-
|
90
|
-
params[:read_timeout] ||= 60
|
91
|
-
params[:connect_timeout] ||= 30
|
92
|
-
params[:connect_retry_interval] ||= 0.1
|
93
|
-
params[:connect_retry_count] ||= 5
|
94
|
-
|
95
|
-
# If Server name and port of where Skynet Service is running
|
96
|
-
# is not supplied look for it in Doozer
|
97
|
-
unless params[:server] || params[:servers]
|
98
|
-
params[:server] = self.class.server_for(service_name)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Disable buffering the send since it is a RPC call
|
102
|
-
params[:buffered] = false
|
103
|
-
|
104
|
-
@logger.trace "Socket Connection parameters", params
|
105
|
-
|
106
|
-
# For each new connection perform the Skynet handshake
|
107
|
-
params[:on_connect] = Proc.new do |socket|
|
108
|
-
# Reset user_data on each connection
|
109
|
-
socket.user_data = 0
|
110
|
-
|
111
|
-
# Receive Service Handshake
|
112
|
-
# Registered bool
|
113
|
-
# Registered indicates the state of this service. If it is false, the connection will
|
114
|
-
# close immediately and the client should look elsewhere for this service.
|
115
|
-
#
|
116
|
-
# ClientID string
|
117
|
-
# ClientID is a UUID that is used by the client to identify itself in RPC requests.
|
118
|
-
@logger.debug "Waiting for Service Handshake"
|
119
|
-
service_handshake = self.class.read_bson_document(socket)
|
120
|
-
@logger.trace 'Service Handshake', service_handshake
|
121
|
-
|
122
|
-
# #TODO When a reconnect returns registered == false we need to go back to doozer
|
123
|
-
@registered = service_handshake['registered']
|
124
|
-
@client_id = service_handshake['clientid']
|
125
|
-
|
126
|
-
# Send blank ClientHandshake
|
127
|
-
client_handshake = { 'clientid' => @client_id }
|
128
|
-
@logger.debug "Sending Client Handshake"
|
129
|
-
@logger.trace 'Client Handshake', client_handshake
|
130
|
-
socket.write(BSON.serialize(client_handshake))
|
131
|
-
end
|
132
|
-
|
133
|
-
@socket = ResilientSocket::TCPClient.new(params)
|
40
|
+
@logger = SemanticLogger::Logger.new("#{self.class.name}: #{service_name}/#{version}/#{region}")
|
41
|
+
@version = version
|
42
|
+
@region = region
|
134
43
|
end
|
135
44
|
|
136
45
|
# Performs a synchronous call to the Skynet Service
|
@@ -145,149 +54,28 @@ module RubySkynet
|
|
145
54
|
#
|
146
55
|
# Raises RubySkynet::ProtocolError
|
147
56
|
# Raises RubySkynet::SkynetException
|
148
|
-
def call(method_name, parameters)
|
57
|
+
def call(method_name, parameters, connection_params={})
|
149
58
|
# Skynet requires BSON RPC Calls to have the following format:
|
150
59
|
# https://github.com/bketelsen/skynet/blob/protocol/protocol.md
|
151
60
|
request_id = BSON::ObjectId.new.to_s
|
152
61
|
@logger.tagged request_id do
|
153
62
|
@logger.benchmark_info "Called Skynet Service: #{@service_name}.#{method_name}" do
|
154
|
-
|
155
|
-
#
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
@logger.trace 'Header', header
|
167
|
-
socket.write(BSON.serialize(header))
|
168
|
-
|
169
|
-
@logger.trace 'Parameters:', parameters
|
170
|
-
|
171
|
-
# The parameters are placed in the request object in BSON serialized
|
172
|
-
# form
|
173
|
-
request = {
|
174
|
-
'clientid' => @client_id,
|
175
|
-
'in' => BSON.serialize(parameters).to_s,
|
176
|
-
'method' => method_name.to_s,
|
177
|
-
'requestinfo' => {
|
178
|
-
'requestid' => request_id,
|
179
|
-
# Increment retry count to indicate that the request may have been tried previously
|
180
|
-
# TODO: this should be incremented if request is retried,
|
181
|
-
'retrycount' => retry_count,
|
182
|
-
# TODO: this should be forwarded along in case of services also
|
183
|
-
# being a client and calling additional services. If empty it will
|
184
|
-
# be stuffed with connecting address
|
185
|
-
'originaddress' => ''
|
186
|
-
}
|
187
|
-
}
|
188
|
-
|
189
|
-
@logger.debug "Sending Request"
|
190
|
-
@logger.trace 'Request', request
|
191
|
-
socket.write(BSON.serialize(request))
|
192
|
-
end
|
193
|
-
|
194
|
-
# Once send is successful it could have been processed, so we can no
|
195
|
-
# longer retry now otherwise we could create a duplicate
|
196
|
-
# retry_count += 1
|
197
|
-
|
198
|
-
# Read header first as a separate BSON document
|
199
|
-
@logger.debug "Reading header from server"
|
200
|
-
header = self.class.read_bson_document(@socket)
|
201
|
-
@logger.debug 'Header', header
|
202
|
-
|
203
|
-
# Read the BSON response document
|
204
|
-
@logger.debug "Reading response from server"
|
205
|
-
response = self.class.read_bson_document(@socket)
|
206
|
-
@logger.trace 'Response', response
|
207
|
-
|
208
|
-
# Ensure the sequence number in the response header matches the
|
209
|
-
# sequence number sent in the request
|
210
|
-
if seq_no = header['seq']
|
211
|
-
raise ProtocolError.new("Incorrect Response received, expected seq=#{@socket.user_data}, received: #{header.inspect}") if seq_no != @socket.user_data
|
212
|
-
else
|
213
|
-
raise ProtocolError.new("Invalid Response header, missing 'seq': #{header.inspect}")
|
214
|
-
end
|
215
|
-
|
216
|
-
# Increment Sequence number only on successful response
|
217
|
-
@socket.user_data += 1
|
218
|
-
|
219
|
-
# If an error is returned from Skynet raise a Skynet exception
|
220
|
-
if error = header['error']
|
221
|
-
raise SkynetException.new(error) if error.to_s.length > 0
|
222
|
-
end
|
223
|
-
|
224
|
-
# If an error is returned from the service raise a Service exception
|
225
|
-
if error = response['error']
|
226
|
-
raise ServiceException.new(error) if error.to_s.length > 0
|
63
|
+
retries = 0
|
64
|
+
# If it cannot connect to a server, try a different server
|
65
|
+
begin
|
66
|
+
Connection.with_connection(Registry.server_for(@service_name, @version, @region), connection_params) do |connection|
|
67
|
+
connection.rpc_call(request_id, @service_name, method_name, parameters)
|
68
|
+
end
|
69
|
+
rescue ResilientSocket::ConnectionFailure => exc
|
70
|
+
if (retries < 3) && exc.cause.is_a?(Errno::ECONNREFUSED)
|
71
|
+
retries += 1
|
72
|
+
retry
|
73
|
+
end
|
74
|
+
# TODO rescue ServiceUnavailable retry x times until the service becomes available
|
227
75
|
end
|
228
|
-
|
229
|
-
# Return Value
|
230
|
-
# The return value is inside the response object, it's a byte array of it's own and needs to be deserialized
|
231
|
-
result = BSON.deserialize(response['out'])
|
232
|
-
@logger.trace 'Return Value', result
|
233
|
-
result
|
234
76
|
end
|
235
77
|
end
|
236
78
|
end
|
237
79
|
|
238
|
-
# Returns a BSON document read from the socket.
|
239
|
-
# Returns nil if the operation times out or if a network
|
240
|
-
# connection failure occurs
|
241
|
-
def self.read_bson_document(socket)
|
242
|
-
bytebuf = BSON::ByteBuffer.new
|
243
|
-
# Read 4 byte size of following BSON document
|
244
|
-
bytes = ''
|
245
|
-
socket.read(4, bytes)
|
246
|
-
|
247
|
-
# Read BSON document
|
248
|
-
sz = bytes.unpack("V")[0]
|
249
|
-
raise "Invalid Data received from server:#{bytes.inspect}" unless sz
|
250
|
-
|
251
|
-
bytebuf.append!(bytes)
|
252
|
-
bytes = ''
|
253
|
-
sz -= 4
|
254
|
-
until bytes.size >= sz
|
255
|
-
buf = ''
|
256
|
-
socket.read(sz, buf)
|
257
|
-
bytes << buf
|
258
|
-
end
|
259
|
-
bytebuf.append!(bytes)
|
260
|
-
return BSON.deserialize(bytebuf)
|
261
|
-
end
|
262
|
-
|
263
|
-
def close()
|
264
|
-
@socket.close
|
265
|
-
end
|
266
|
-
|
267
|
-
##############################
|
268
|
-
#protected
|
269
|
-
|
270
|
-
# Returns [Array] of the hostname and port pair [String] that implements a particular service
|
271
|
-
# Performs a doozer lookup to find the servers
|
272
|
-
#
|
273
|
-
# service_name:
|
274
|
-
# version: Version of service to locate
|
275
|
-
# Default: Find latest version
|
276
|
-
def self.registered_implementers(service_name, version = '*', region = 'Development')
|
277
|
-
hosts = []
|
278
|
-
doozer.walk("/services/#{service_name}/#{version}/#{region}/*/*").each do |node|
|
279
|
-
entry = MultiJson.load(node.value)
|
280
|
-
hosts << entry if entry['Registered']
|
281
|
-
end
|
282
|
-
hosts
|
283
|
-
end
|
284
|
-
|
285
|
-
# Randomly returns a server that implements the requested service
|
286
|
-
def self.server_for(service_name, version = '*', region = 'Development')
|
287
|
-
hosts = registered_implementers(service_name, version, region)
|
288
|
-
service = hosts[rand(hosts.size)]['Config']['ServiceAddr']
|
289
|
-
"#{service['IPAddress']}:#{service['Port']}"
|
290
|
-
end
|
291
|
-
|
292
80
|
end
|
293
81
|
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'bson'
|
2
|
+
require 'gene_pool'
|
3
|
+
require 'thread_safe'
|
4
|
+
|
5
|
+
#
|
6
|
+
# RubySkynet Connection
|
7
|
+
#
|
8
|
+
# Handles connecting to Skynet Servers as a host:port pair
|
9
|
+
#
|
10
|
+
module RubySkynet
|
11
|
+
class Connection
|
12
|
+
include SyncAttr
|
13
|
+
|
14
|
+
# Returns the underlying socket being used by a Connection instance
|
15
|
+
attr_reader :socket
|
16
|
+
|
17
|
+
# Default Pool configuration
|
18
|
+
sync_cattr_accessor :pool_config do
|
19
|
+
{
|
20
|
+
:pool_size => 30, # Maximum number of connections to any one server
|
21
|
+
:warn_timeout => 2, # Log a warning if no connections are available after the :warn_timeout seconds
|
22
|
+
:timeout => 10, # Raise a Timeout exception if no connections are available after the :timeout seconds
|
23
|
+
:idle_timeout => 600, # Renew a connection if it has been idle for this period of time
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Logging instance for the connection pool
|
28
|
+
sync_cattr_reader :logger do
|
29
|
+
SemanticLogger::Logger.new(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
# For each server there is a connection pool keyed on the
|
33
|
+
# server address: 'host:port'
|
34
|
+
@@connection_pools = ThreadSafe::Hash.new
|
35
|
+
|
36
|
+
# Returns a new RubySkynet connection to the server
|
37
|
+
#
|
38
|
+
# Parameters:
|
39
|
+
# :read_timeout [Float]
|
40
|
+
# Time in seconds to timeout on read
|
41
|
+
# Can be overridden by supplying a timeout in the read call
|
42
|
+
# Default: 60
|
43
|
+
#
|
44
|
+
# :connect_timeout [Float]
|
45
|
+
# Time in seconds to timeout when trying to connect to the server
|
46
|
+
# Default: Half of the :read_timeout ( 30 seconds )
|
47
|
+
#
|
48
|
+
# :connect_retry_count [Fixnum]
|
49
|
+
# Number of times to retry connecting when a connection fails
|
50
|
+
# Default: 10
|
51
|
+
#
|
52
|
+
# :connect_retry_interval [Float]
|
53
|
+
# Number of seconds between connection retry attempts after the first failed attempt
|
54
|
+
# Default: 0.5
|
55
|
+
def initialize(server, params = {})
|
56
|
+
@logger = SemanticLogger::Logger.new("#{self.class.name}: #{server}")
|
57
|
+
|
58
|
+
# User configurable options
|
59
|
+
params[:read_timeout] ||= 60
|
60
|
+
params[:connect_timeout] ||= 30
|
61
|
+
params[:connect_retry_interval] ||= 0.1
|
62
|
+
params[:connect_retry_count] ||= 5
|
63
|
+
|
64
|
+
# Disable send buffering since it is a RPC call
|
65
|
+
params[:buffered] = false
|
66
|
+
|
67
|
+
# For each new connection perform the Skynet handshake
|
68
|
+
params[:on_connect] = Proc.new do |socket|
|
69
|
+
# Reset user_data on each connection
|
70
|
+
socket.user_data = {
|
71
|
+
:seq => 0,
|
72
|
+
:logger => @logger
|
73
|
+
}
|
74
|
+
|
75
|
+
# Receive Service Handshake
|
76
|
+
# Registered bool
|
77
|
+
# Registered indicates the state of this service. If it is false, the connection will
|
78
|
+
# close immediately and the client should look elsewhere for this service.
|
79
|
+
#
|
80
|
+
# ClientID string
|
81
|
+
# ClientID is a UUID that is used by the client to identify itself in RPC requests.
|
82
|
+
@logger.debug "Waiting for Service Handshake"
|
83
|
+
service_handshake = self.class.read_bson_document(socket)
|
84
|
+
@logger.trace 'Service Handshake', service_handshake
|
85
|
+
|
86
|
+
# #TODO When a reconnect returns registered == false need to throw an exception
|
87
|
+
# so that this host connection is not used
|
88
|
+
registered = service_handshake['registered']
|
89
|
+
client_id = service_handshake['clientid']
|
90
|
+
socket.user_data[:client_id] = client_id
|
91
|
+
|
92
|
+
# Send blank ClientHandshake
|
93
|
+
client_handshake = { 'clientid' => client_id }
|
94
|
+
@logger.debug "Sending Client Handshake"
|
95
|
+
@logger.trace 'Client Handshake', client_handshake
|
96
|
+
socket.write(BSON.serialize(client_handshake))
|
97
|
+
end
|
98
|
+
|
99
|
+
# To prevent strange issues if user incorrectly supplies server names
|
100
|
+
params.delete(:servers)
|
101
|
+
params[:server] = server
|
102
|
+
|
103
|
+
@socket = ResilientSocket::TCPClient.new(params)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Performs a synchronous call to a Skynet server
|
107
|
+
#
|
108
|
+
# Parameters:
|
109
|
+
# service_name [String|Symbol]:
|
110
|
+
# Name of the method to pass in the request
|
111
|
+
# method_name [String|Symbol]:
|
112
|
+
# Name of the method to pass in the request
|
113
|
+
# parameters [Hash]:
|
114
|
+
# Parameters to pass in the request
|
115
|
+
# idempotent [True|False]:
|
116
|
+
# If the request can be applied again to the server without changing its state
|
117
|
+
# Set to true to retry the entire request after the send is successful
|
118
|
+
#
|
119
|
+
# Returns the Hash result returned from the Skynet Service
|
120
|
+
#
|
121
|
+
# Raises RubySkynet::ProtocolError
|
122
|
+
# Raises RubySkynet::SkynetException
|
123
|
+
def rpc_call(request_id, service_name, method_name, parameters, idempotent=false)
|
124
|
+
retry_count = 0
|
125
|
+
header = nil
|
126
|
+
response = nil
|
127
|
+
|
128
|
+
socket.retry_on_connection_failure do |socket|
|
129
|
+
header = {
|
130
|
+
'servicemethod' => "#{service_name}.Forward",
|
131
|
+
'seq' => socket.user_data[:seq]
|
132
|
+
}
|
133
|
+
|
134
|
+
@logger.debug "Sending Header"
|
135
|
+
@logger.trace 'Header', header
|
136
|
+
socket.write(BSON.serialize(header))
|
137
|
+
|
138
|
+
# The parameters are placed in the request object in BSON serialized form
|
139
|
+
request = {
|
140
|
+
'clientid' => socket.user_data[:client_id],
|
141
|
+
'in' => BSON.serialize(parameters).to_s,
|
142
|
+
'method' => method_name.to_s,
|
143
|
+
'requestinfo' => {
|
144
|
+
'requestid' => request_id,
|
145
|
+
# Increment retry count to indicate that the request may have been tried previously
|
146
|
+
'retrycount' => retry_count,
|
147
|
+
# TODO: this should be forwarded along in case of services also
|
148
|
+
# being a client and calling additional services. If empty it will
|
149
|
+
# be stuffed with connecting address
|
150
|
+
'originaddress' => ''
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
@logger.debug "Sending Request"
|
155
|
+
@logger.trace 'Request', request
|
156
|
+
@logger.trace 'Parameters:', parameters
|
157
|
+
socket.write(BSON.serialize(request))
|
158
|
+
|
159
|
+
# Since Send does not affect state on the server we can also retry reads
|
160
|
+
if idempotent
|
161
|
+
@logger.debug "Reading header from server"
|
162
|
+
header = self.class.read_bson_document(socket)
|
163
|
+
@logger.debug 'Response Header', header
|
164
|
+
|
165
|
+
# Read the BSON response document
|
166
|
+
@logger.debug "Reading response from server"
|
167
|
+
response = self.class.read_bson_document(socket)
|
168
|
+
@logger.trace 'Response', response
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Perform the read outside the retry block since a successful write
|
173
|
+
# means that the servers state may have been changed
|
174
|
+
unless idempotent
|
175
|
+
# Read header first as a separate BSON document
|
176
|
+
@logger.debug "Reading header from server"
|
177
|
+
header = self.class.read_bson_document(socket)
|
178
|
+
@logger.debug 'Response Header', header
|
179
|
+
|
180
|
+
# Read the BSON response document
|
181
|
+
@logger.debug "Reading response from server"
|
182
|
+
response = self.class.read_bson_document(socket)
|
183
|
+
@logger.trace 'Response', response
|
184
|
+
end
|
185
|
+
|
186
|
+
# Ensure the sequence number in the response header matches the
|
187
|
+
# sequence number sent in the request
|
188
|
+
seq_no = header['seq']
|
189
|
+
if seq_no != socket.user_data[:seq]
|
190
|
+
raise ProtocolError.new("Incorrect Response received, expected seq=#{socket.user_data[:seq]}, received: #{header.inspect}")
|
191
|
+
end
|
192
|
+
|
193
|
+
# Increment Sequence number only on successful response
|
194
|
+
socket.user_data[:seq] += 1
|
195
|
+
|
196
|
+
# If an error is returned from Skynet raise a Skynet exception
|
197
|
+
error = header['error']
|
198
|
+
raise SkynetException.new(error) if error.to_s.length > 0
|
199
|
+
|
200
|
+
# If an error is returned from the service raise a Service exception
|
201
|
+
error = response['error']
|
202
|
+
raise ServiceException.new(error) if error.to_s.length > 0
|
203
|
+
|
204
|
+
# Return Value
|
205
|
+
# The return value is inside the response object, it's a byte array of it's own and needs to be deserialized
|
206
|
+
result = BSON.deserialize(response['out'])
|
207
|
+
@logger.trace 'Return Value', result
|
208
|
+
result
|
209
|
+
end
|
210
|
+
|
211
|
+
# Execute the supplied block with a connection from the pool
|
212
|
+
def self.with_connection(server, params={}, &block)
|
213
|
+
(@@connection_pools[server] ||= new_connection_pool(server, params)).with_connection(&block)
|
214
|
+
end
|
215
|
+
|
216
|
+
def close
|
217
|
+
@socket.close if @socket
|
218
|
+
end
|
219
|
+
|
220
|
+
########################
|
221
|
+
protected
|
222
|
+
|
223
|
+
# Returns a BSON document read from the socket.
|
224
|
+
# Returns nil if the operation times out or if a network
|
225
|
+
# connection failure occurs
|
226
|
+
def self.read_bson_document(socket)
|
227
|
+
bytebuf = BSON::ByteBuffer.new
|
228
|
+
# Read 4 byte size of following BSON document
|
229
|
+
bytes = socket.read(4)
|
230
|
+
|
231
|
+
# Read BSON document
|
232
|
+
sz = bytes.unpack("V")[0]
|
233
|
+
raise "Invalid Data received from server:#{bytes.inspect}" unless sz
|
234
|
+
|
235
|
+
bytebuf.append!(bytes)
|
236
|
+
bytebuf.append!(socket.read(sz - 4))
|
237
|
+
return BSON.deserialize(bytebuf)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns a new connection pool for the specified server
|
241
|
+
def self.new_connection_pool(server, params={})
|
242
|
+
# Connection pool configuration options
|
243
|
+
config = pool_config.dup
|
244
|
+
|
245
|
+
# Method to call to close idle connections
|
246
|
+
config[:close_proc] = :close
|
247
|
+
config[:logger] = logger
|
248
|
+
config[:name] = "Connection pool for #{server}"
|
249
|
+
|
250
|
+
pool = GenePool.new(pool_config) do
|
251
|
+
new(server, params)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Cleanup corresponding connection pool when a server terminates
|
255
|
+
Registry.on_server_removed(server) do
|
256
|
+
pool = @@connection_pools.delete(server)
|
257
|
+
# Cannot close all the connections since they could still be in use
|
258
|
+
pool.remove_idle(0) if pool
|
259
|
+
#pool.close if pool
|
260
|
+
logger.debug "Connection pool for server:#{server} has been released"
|
261
|
+
end
|
262
|
+
|
263
|
+
pool
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
@@ -142,14 +142,29 @@ module RubySkynet
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
-
#
|
146
|
-
#
|
147
|
-
|
148
|
-
|
149
|
-
|
145
|
+
# Wait for changes to the supplied path
|
146
|
+
# Returns the next change to the supplied path
|
147
|
+
def wait(path, rev=current_revision, timeout=-1)
|
148
|
+
invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::WAIT), true, timeout)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Watch for any changes to the supplied path, calling the supplied block
|
152
|
+
# for every change
|
153
|
+
# Runs until an exception is thrown
|
154
|
+
#
|
155
|
+
# If a connection error occurs it will create a new connection to doozer
|
156
|
+
# and resubmit the wait. I.e. Will continue from where it left off
|
157
|
+
# without any noticeable effect to the supplied block
|
158
|
+
def watch(path, rev=current_revision)
|
159
|
+
loop do
|
160
|
+
result = wait(path, rev, -1)
|
161
|
+
yield result
|
162
|
+
rev = result.rev + 1
|
163
|
+
end
|
164
|
+
end
|
150
165
|
|
151
166
|
#####################
|
152
|
-
#
|
167
|
+
#protected
|
153
168
|
|
154
169
|
# Call the Doozer server
|
155
170
|
#
|
@@ -158,16 +173,16 @@ module RubySkynet
|
|
158
173
|
# _only_ if a rev has been supplied
|
159
174
|
#
|
160
175
|
# When modifier is true
|
161
|
-
def invoke(request, readonly=true)
|
176
|
+
def invoke(request, readonly=true, timeout=nil)
|
162
177
|
retry_read = readonly || !request.rev.nil?
|
163
178
|
response = nil
|
164
179
|
@socket.retry_on_connection_failure do
|
165
180
|
send(request)
|
166
|
-
response = read if retry_read
|
181
|
+
response = read(timeout) if retry_read
|
167
182
|
end
|
168
183
|
# Network error on read must be sent back to caller since we do not
|
169
184
|
# know if the modification was made
|
170
|
-
response = read unless retry_read
|
185
|
+
response = read(timeout) unless retry_read
|
171
186
|
raise ResponseError.new("#{Response::Err.name_by_value(response.err_code)}: #{response.err_detail}") if response.err_code != 0
|
172
187
|
response
|
173
188
|
end
|
@@ -182,19 +197,11 @@ module RubySkynet
|
|
182
197
|
end
|
183
198
|
|
184
199
|
# Read the protobuf Response from Doozer
|
185
|
-
def read
|
200
|
+
def read(timeout=nil)
|
186
201
|
# First strip the additional header indicating the size of the subsequent response
|
187
|
-
head = @socket.read(4)
|
202
|
+
head = @socket.read(4,nil,timeout)
|
188
203
|
length = head.unpack("N")[0]
|
189
|
-
|
190
|
-
# Since can returns upto 'length' bytes we need to make sure it returns
|
191
|
-
# at least 'length' bytes
|
192
|
-
# TODO: Make this a binary buffer
|
193
|
-
data = ''
|
194
|
-
until data.size >= length
|
195
|
-
data << @socket.read(length)
|
196
|
-
end
|
197
|
-
Response.new.parse_from_string(data)
|
204
|
+
Response.new.parse_from_string(@socket.read(length))
|
198
205
|
end
|
199
206
|
|
200
207
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module RubySkynet
|
2
2
|
class Exception < ::RuntimeError; end
|
3
3
|
class ProtocolError < Exception; end
|
4
|
-
class SkynetException < Exception; end
|
5
4
|
class ServiceException < Exception; end
|
5
|
+
class SkynetException < Exception; end
|
6
|
+
class ServiceUnavailable < SkynetException; end
|
6
7
|
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'sync_attr'
|
2
|
+
require 'multi_json'
|
3
|
+
require 'thread_safe'
|
4
|
+
require 'gene_pool'
|
5
|
+
|
6
|
+
#
|
7
|
+
# RubySkynet Registry Client
|
8
|
+
#
|
9
|
+
# Keeps a local copy of the Skynet Registry
|
10
|
+
#
|
11
|
+
# Subscribes to Registry changes and the internal copy up to date
|
12
|
+
#
|
13
|
+
module RubySkynet
|
14
|
+
class Registry
|
15
|
+
include SyncAttr
|
16
|
+
|
17
|
+
# Service Registry has the following format
|
18
|
+
# Key: [String] 'service_name/version/region'
|
19
|
+
# Value: [Array<String>] 'host:port', 'host:port'
|
20
|
+
sync_cattr_accessor :service_registry do
|
21
|
+
start_monitoring
|
22
|
+
end
|
23
|
+
|
24
|
+
@@on_server_removed_callbacks = ThreadSafe::Hash.new
|
25
|
+
@@monitor_thread = nil
|
26
|
+
|
27
|
+
DOOZER_SERVICES_PATH = "/services/*/*/*/*/*"
|
28
|
+
|
29
|
+
# Default doozer configuration
|
30
|
+
# To replace this default, set the config as follows:
|
31
|
+
# RubySkynet::Client.doozer_config = { .... }
|
32
|
+
#
|
33
|
+
# :servers [Array of String]
|
34
|
+
# Array of URL's of doozer servers to connect to with port numbers
|
35
|
+
# ['server1:2000', 'server2:2000']
|
36
|
+
#
|
37
|
+
# The second server will only be attempted once the first server
|
38
|
+
# cannot be connected to or has timed out on connect
|
39
|
+
# A read failure or timeout will not result in switching to the second
|
40
|
+
# server, only a connection failure or during an automatic reconnect
|
41
|
+
#
|
42
|
+
# :read_timeout [Float]
|
43
|
+
# Time in seconds to timeout on read
|
44
|
+
# Can be overridden by supplying a timeout in the read call
|
45
|
+
#
|
46
|
+
# :connect_timeout [Float]
|
47
|
+
# Time in seconds to timeout when trying to connect to the server
|
48
|
+
#
|
49
|
+
# :connect_retry_count [Fixnum]
|
50
|
+
# Number of times to retry connecting when a connection fails
|
51
|
+
#
|
52
|
+
# :connect_retry_interval [Float]
|
53
|
+
# Number of seconds between connection retry attempts after the first failed attempt
|
54
|
+
sync_cattr_accessor :doozer_config do
|
55
|
+
{
|
56
|
+
:servers => ['127.0.0.1:8046'],
|
57
|
+
:read_timeout => 5,
|
58
|
+
:connect_timeout => 3,
|
59
|
+
:connect_retry_interval => 1,
|
60
|
+
:connect_retry_count => 300
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Lazy initialize Doozer Client Connection pool
|
65
|
+
sync_cattr_reader :doozer_pool do
|
66
|
+
GenePool.new(
|
67
|
+
:name =>"Doozer Connection Pool",
|
68
|
+
:pool_size => 5,
|
69
|
+
:timeout => 30,
|
70
|
+
:warn_timeout => 5,
|
71
|
+
:idle_timeout => 600,
|
72
|
+
:logger => logger,
|
73
|
+
:close_proc => :close
|
74
|
+
) do
|
75
|
+
Doozer::Client.new(doozer_config)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Logging instance for this class
|
80
|
+
sync_cattr_reader :logger do
|
81
|
+
SemanticLogger::Logger.new(self, :debug)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return a server that implements the specified service
|
85
|
+
def self.server_for(service_name, version='*', region='Development')
|
86
|
+
if servers = servers_for(service_name, version, region)
|
87
|
+
# Randomly select one of the servers offering the service
|
88
|
+
servers[rand(servers.size)]
|
89
|
+
else
|
90
|
+
msg = "No servers available for service: #{service_name} with version: #{version} in region: #{region}"
|
91
|
+
logger.warn msg
|
92
|
+
raise ServiceUnavailable.new(msg)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns [Array] of the hostname and port pair [String] that implements a particular service
|
97
|
+
# Performs a doozer lookup to find the servers
|
98
|
+
#
|
99
|
+
# service_name:
|
100
|
+
# Name of the service to lookup
|
101
|
+
# version:
|
102
|
+
# Version of service to locate
|
103
|
+
# Default: All versions
|
104
|
+
# region:
|
105
|
+
# Region to look for the service in
|
106
|
+
def self.registered_implementers(service_name='*', version='*', region='Development')
|
107
|
+
hosts = []
|
108
|
+
doozer_pool.with_connection do |doozer|
|
109
|
+
doozer.walk("/services/#{service_name}/#{version}/#{region}/*/*").each do |node|
|
110
|
+
entry = MultiJson.load(node.value)
|
111
|
+
hosts << entry if entry['Registered']
|
112
|
+
end
|
113
|
+
end
|
114
|
+
hosts
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns [Array<String>] a list of servers implementing the requested service
|
118
|
+
def self.servers_for(service_name, version='*', region='Development', remote = false)
|
119
|
+
if remote
|
120
|
+
if version != '*'
|
121
|
+
registered_implementers(service_name, version, region).map do |host|
|
122
|
+
service = host['Config']['ServiceAddr']
|
123
|
+
"#{service['IPAddress']}:#{service['Port']}"
|
124
|
+
end
|
125
|
+
else
|
126
|
+
# Find the highest version of any particular service
|
127
|
+
versions = {}
|
128
|
+
registered_implementers(service_name, version, region).each do |host|
|
129
|
+
service = host['Config']['ServiceAddr']
|
130
|
+
(versions[version.to_i] ||= []) << "#{service['IPAddress']}:#{service['Port']}"
|
131
|
+
end
|
132
|
+
# Return the servers implementing the highest version number
|
133
|
+
versions.sort.last.last
|
134
|
+
end
|
135
|
+
else
|
136
|
+
if version == '*'
|
137
|
+
# Find the highest version for the named service in this region
|
138
|
+
version = -1
|
139
|
+
service_registry.keys.each do |key|
|
140
|
+
if match = key.match(/#{service_name}\/(\d+)\/#{region}/)
|
141
|
+
ver = match[1].to_i
|
142
|
+
version = ver if ver > version
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
service_registry["#{service_name}/#{version}/#{region}"]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Invokes registered callbacks when a specific server is shutdown or terminates
|
151
|
+
# Not when a server de-registers itself
|
152
|
+
# The callback will only be called once and will need to be re-registered
|
153
|
+
# after being called if future callbacks are required for that server
|
154
|
+
def self.on_server_removed(server, &block)
|
155
|
+
(@@on_server_removed_callbacks[server] ||= ThreadSafe::Array.new) << block
|
156
|
+
end
|
157
|
+
|
158
|
+
############################
|
159
|
+
#protected
|
160
|
+
|
161
|
+
# Fetch the all registry information from Doozer and set the internal registry
|
162
|
+
# Also starts the monitoring thread to keep the registry up to date
|
163
|
+
def self.start_monitoring
|
164
|
+
registry = ThreadSafe::Hash.new
|
165
|
+
revision = nil
|
166
|
+
doozer_pool.with_connection do |doozer|
|
167
|
+
revision = doozer.current_revision
|
168
|
+
doozer.walk(DOOZER_SERVICES_PATH, revision).each do |node|
|
169
|
+
# path: "/services/TutorialService/1/Development/127.0.0.1/9000"
|
170
|
+
e = node.path.split('/')
|
171
|
+
|
172
|
+
# Key: [String] 'service_name/version/region'
|
173
|
+
key = "#{e[2]}/#{e[3]}/#{e[4]}"
|
174
|
+
server = "#{e[5]}:#{e[6]}"
|
175
|
+
|
176
|
+
if node.value.strip.size > 0
|
177
|
+
entry = MultiJson.load(node.value)
|
178
|
+
if entry['Registered']
|
179
|
+
# Value: [Array<String>] 'host:port', 'host:port'
|
180
|
+
servers = (registry[key] ||= ThreadSafe::Array.new)
|
181
|
+
servers << server unless servers.include?(server)
|
182
|
+
logger.debug "#start_monitoring Add Service: #{key} => #{server}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
# Start monitoring thread to keep the registry up to date
|
188
|
+
@@monitor_thread = Thread.new { self.watch(revision + 1) }
|
189
|
+
registry
|
190
|
+
end
|
191
|
+
|
192
|
+
# Waits for any updates from Doozer and updates the internal service registry
|
193
|
+
def self.watch(revision)
|
194
|
+
logger.info "Start monitoring #{DOOZER_SERVICES_PATH}"
|
195
|
+
# This thread must use its own dedicated doozer connection
|
196
|
+
doozer = Doozer::Client.new(doozer_config)
|
197
|
+
doozer.watch(DOOZER_SERVICES_PATH, revision) do |node|
|
198
|
+
# path: "/services/TutorialService/1/Development/127.0.0.1/9000"
|
199
|
+
e = node.path.split('/')
|
200
|
+
|
201
|
+
# Key: [String] 'service_name/version/region'
|
202
|
+
key = "#{e[2]}/#{e[3]}/#{e[4]}"
|
203
|
+
server = "#{e[5]}:#{e[6]}"
|
204
|
+
|
205
|
+
if node.value.strip.size > 0
|
206
|
+
entry = MultiJson.load(node.value)
|
207
|
+
if entry['Registered']
|
208
|
+
# Value: [Array<String>] 'host:port', 'host:port'
|
209
|
+
servers = (@@service_registry[key] ||= ThreadSafe::Array.new)
|
210
|
+
servers << server unless servers.include?(server)
|
211
|
+
logger.debug "#monitor Add/Update Service: #{key} => #{server}"
|
212
|
+
else
|
213
|
+
logger.debug "#monitor Service deregistered, remove: #{key} => #{server}"
|
214
|
+
if @@service_registry[key]
|
215
|
+
@@service_registry[key].delete(server)
|
216
|
+
@@service_registry.delete(key) if @@service_registry[key].size == 0
|
217
|
+
end
|
218
|
+
end
|
219
|
+
else
|
220
|
+
# Service has stopped and needs to be removed
|
221
|
+
logger.debug "#monitor Service stopped, remove: #{key} => #{server}"
|
222
|
+
if @@service_registry[key]
|
223
|
+
@@service_registry[key].delete(server)
|
224
|
+
@@service_registry.delete(key) if @@service_registry[key].size == 0
|
225
|
+
server_removed(server)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
logger.debug "Updated registry", @@service_registry
|
229
|
+
end
|
230
|
+
logger.info "Stopping monitoring thread normally"
|
231
|
+
rescue Exception => exc
|
232
|
+
logger.error "Exception in monitoring thread", exc
|
233
|
+
ensure
|
234
|
+
logger.info "Stopped monitoring"
|
235
|
+
end
|
236
|
+
|
237
|
+
# Invoke any registered callbacks for the specific server
|
238
|
+
def self.server_removed(server)
|
239
|
+
if callbacks = @@on_server_removed_callbacks.delete(server)
|
240
|
+
callbacks.each do |block|
|
241
|
+
begin
|
242
|
+
logger.info "Calling callback for server: #{server}"
|
243
|
+
block.call(server)
|
244
|
+
rescue Exception => exc
|
245
|
+
logger.error("Exception during a callback for server: #{server}", exc)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
data/lib/ruby_skynet/version.rb
CHANGED
data/lib/ruby_skynet.rb
CHANGED
@@ -7,5 +7,7 @@ module RubySkynet
|
|
7
7
|
module Doozer
|
8
8
|
autoload :Client, 'ruby_skynet/doozer/client'
|
9
9
|
end
|
10
|
-
autoload :
|
10
|
+
autoload :Registry, 'ruby_skynet/registry'
|
11
|
+
autoload :Connection, 'ruby_skynet/connection'
|
12
|
+
autoload :Client, 'ruby_skynet/client'
|
11
13
|
end
|
@@ -7,6 +7,7 @@ require 'test/unit'
|
|
7
7
|
require 'shoulda'
|
8
8
|
require 'ruby_skynet'
|
9
9
|
require 'simple_server'
|
10
|
+
require 'multi_json'
|
10
11
|
|
11
12
|
SemanticLogger::Logger.default_level = :trace
|
12
13
|
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
|
@@ -17,43 +18,57 @@ class RubySkynetClientTest < Test::Unit::TestCase
|
|
17
18
|
|
18
19
|
context "without server" do
|
19
20
|
should "raise exception when cannot reach server after 5 retries" do
|
20
|
-
exception = assert_raise
|
21
|
-
RubySkynet::Client.new('SomeService'
|
22
|
-
|
23
|
-
:connect_retry_interval => 0.1,
|
24
|
-
:connect_retry_count => 5)
|
21
|
+
exception = assert_raise RubySkynet::ServiceUnavailable do
|
22
|
+
client = RubySkynet::Client.new('SomeService')
|
23
|
+
client.call(:test, :hello => 'there')
|
25
24
|
end
|
26
|
-
assert_match /
|
25
|
+
assert_match /No servers available for service: SomeService with version: \* in region: Development/, exception.message
|
27
26
|
end
|
28
27
|
|
29
28
|
end
|
30
29
|
|
31
30
|
context "with server" do
|
32
31
|
setup do
|
32
|
+
@port = 2000
|
33
33
|
@read_timeout = 3.0
|
34
|
-
@server = SimpleServer.new(
|
35
|
-
@server_name =
|
34
|
+
@server = SimpleServer.new(@port)
|
35
|
+
@server_name = "localhost:#{@port}"
|
36
|
+
|
37
|
+
# Register service in doozer
|
38
|
+
@service_name = "TestService"
|
39
|
+
@version = 1
|
40
|
+
@region = 'Test'
|
41
|
+
@ip_address = "127.0.0.1"
|
42
|
+
config = {
|
43
|
+
"Config" => {
|
44
|
+
"UUID" => "3978b371-15e9-40f8-9b7b-59ae88d8c7ec",
|
45
|
+
"Name" => @service_name,
|
46
|
+
"Version" => @version.to_s,
|
47
|
+
"Region" => @region,
|
48
|
+
"ServiceAddr" => {
|
49
|
+
"IPAddress" => @ip_address,
|
50
|
+
"Port" => @port,
|
51
|
+
"MaxPort" => @port + 999
|
52
|
+
},
|
53
|
+
},
|
54
|
+
"Registered" => true
|
55
|
+
}
|
56
|
+
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
57
|
+
doozer["/services/#{@service_name}/#{@version}/#{@region}/#{@ip_address}/#{@port}"] = MultiJson.encode(config)
|
58
|
+
end
|
36
59
|
end
|
37
60
|
|
38
61
|
teardown do
|
39
62
|
@server.stop if @server
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
should "call server" do
|
44
|
-
RubySkynet::Client.connect('TutorialService', :read_timeout => @read_timeout, :server => @server_name) do |tutorial_service|
|
45
|
-
assert_equal 'test1', tutorial_service.call(:test1, 'some' => 'parameters')['result']
|
46
|
-
end
|
63
|
+
# De-register server in doozer
|
64
|
+
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
65
|
+
doozer.delete("/services/#{@service_name}/#{@version}/#{@region}/#{@ip_address}/#{@port}") rescue nil
|
47
66
|
end
|
48
67
|
end
|
49
68
|
|
50
69
|
context "with client connection" do
|
51
70
|
setup do
|
52
|
-
@client = RubySkynet::Client.new(
|
53
|
-
end
|
54
|
-
|
55
|
-
def teardown
|
56
|
-
@client.close if @client
|
71
|
+
@client = RubySkynet::Client.new(@service_name, @version, @region)
|
57
72
|
end
|
58
73
|
|
59
74
|
should "successfully send and receive data" do
|
@@ -66,7 +81,7 @@ class RubySkynetClientTest < Test::Unit::TestCase
|
|
66
81
|
|
67
82
|
exception = assert_raise ResilientSocket::ReadTimeout do
|
68
83
|
# Read 4 bytes from server
|
69
|
-
@client.call('sleep', request)
|
84
|
+
@client.call('sleep', request, :read_timeout => @read_timeout)
|
70
85
|
end
|
71
86
|
assert_match /Timedout after #{@read_timeout} seconds trying to read/, exception.message
|
72
87
|
end
|
data/test.log
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_skynet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: semantic_logger
|
@@ -98,13 +98,16 @@ executables: []
|
|
98
98
|
extensions: []
|
99
99
|
extra_rdoc_files: []
|
100
100
|
files:
|
101
|
+
- dev.log
|
101
102
|
- Gemfile
|
102
103
|
- Gemfile.lock
|
103
104
|
- lib/ruby_skynet/client.rb
|
105
|
+
- lib/ruby_skynet/connection.rb
|
104
106
|
- lib/ruby_skynet/doozer/client.rb
|
105
107
|
- lib/ruby_skynet/doozer/exceptions.rb
|
106
108
|
- lib/ruby_skynet/doozer/msg.pb.rb
|
107
109
|
- lib/ruby_skynet/exceptions.rb
|
110
|
+
- lib/ruby_skynet/registry.rb
|
108
111
|
- lib/ruby_skynet/version.rb
|
109
112
|
- lib/ruby_skynet.rb
|
110
113
|
- LICENSE.txt
|
@@ -116,8 +119,6 @@ files:
|
|
116
119
|
- nbproject/project.xml
|
117
120
|
- Rakefile
|
118
121
|
- README.md
|
119
|
-
- ruby_skynet-0.1.0.gem
|
120
|
-
- ruby_skynet-0.1.1.gem
|
121
122
|
- test/doozer_client_test.rb
|
122
123
|
- test/ruby_skynet_client_test.rb
|
123
124
|
- test/simple_server.rb
|
@@ -136,7 +137,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
136
137
|
version: '0'
|
137
138
|
segments:
|
138
139
|
- 0
|
139
|
-
hash:
|
140
|
+
hash: -4330021218658134009
|
140
141
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
142
|
none: false
|
142
143
|
requirements:
|
data/ruby_skynet-0.1.0.gem
DELETED
Binary file
|
data/ruby_skynet-0.1.1.gem
DELETED
Binary file
|