ruby_skynet 0.4.0.pre2 → 0.4.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.
- checksums.yaml +4 -4
- data/Gemfile +1 -3
- data/Gemfile.lock +16 -20
- data/Rakefile +0 -1
- data/examples/e.rb +0 -14
- data/lib/ruby_skynet/client.rb +1 -1
- data/lib/ruby_skynet/common.rb +9 -1
- data/lib/ruby_skynet/doozer/client.rb +1 -1
- data/lib/ruby_skynet/exceptions.rb +1 -0
- data/lib/ruby_skynet/registry.rb +220 -97
- data/lib/ruby_skynet/server.rb +72 -90
- data/lib/ruby_skynet/service.rb +4 -8
- data/lib/ruby_skynet/version.rb +1 -1
- data/test/{ruby_skynet_client_test.rb → client_test.rb} +32 -33
- data/test/registry_test.rb +99 -0
- data/test/{ruby_skynet_service_test.rb → service_test.rb} +20 -21
- metadata +7 -21
- data/test/simple_server.rb +0 -122
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16525cda69fa5554ce2851afcea16d4d379e1c3e
|
4
|
+
data.tar.gz: b02c07c9d88035fc5018e18548dc0e868f640716
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 461650dbc05cd7febf853c2f7416e22f089cec61fa4e5aea188cef90573452e6ea1f8cc43443132b94071420e9727cacd605662a2d35423baf3a2acecbc2132e
|
7
|
+
data.tar.gz: 9bfa3b9751bd03753a849e34c1c4a3eb9c9f9de04af1efe34131aa7fdcd3c59aa1f8e3fb885ecbe61e79abd2aea810804a9c169c9a26ed0d553ecf0e5210be23
|
data/Gemfile
CHANGED
@@ -2,6 +2,7 @@ source :rubygems
|
|
2
2
|
|
3
3
|
group :test do
|
4
4
|
gem "shoulda"
|
5
|
+
gem "mocha", :require => false
|
5
6
|
end
|
6
7
|
|
7
8
|
gem "rake"
|
@@ -16,6 +17,3 @@ gem "ruby_protobuf"
|
|
16
17
|
# Wire format when communicating with services
|
17
18
|
gem "bson"
|
18
19
|
gem "bson_ext", :platform => :ruby
|
19
|
-
# Celluloid::IO is used to create SkyNet services in Ruby
|
20
|
-
# multi_json?
|
21
|
-
gem "celluloid-io", '0.13.0.pre2'
|
data/Gemfile.lock
CHANGED
@@ -1,27 +1,23 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (3.2.
|
5
|
-
i18n (
|
4
|
+
activesupport (3.2.13)
|
5
|
+
i18n (= 0.6.1)
|
6
6
|
multi_json (~> 1.0)
|
7
7
|
atomic (1.0.1)
|
8
8
|
atomic (1.0.1-java)
|
9
|
-
bourne (1.
|
10
|
-
mocha (
|
9
|
+
bourne (1.4.0)
|
10
|
+
mocha (~> 0.13.2)
|
11
11
|
bson (1.8.3)
|
12
12
|
bson (1.8.3-java)
|
13
|
-
|
14
|
-
|
15
|
-
celluloid-io (0.13.0.pre2)
|
16
|
-
celluloid (>= 0.13.0.pre)
|
17
|
-
nio4r (>= 0.4.0)
|
13
|
+
bson_ext (1.8.3)
|
14
|
+
bson (~> 1.8.3)
|
18
15
|
gene_pool (1.3.0)
|
19
|
-
i18n (0.6.
|
16
|
+
i18n (0.6.1)
|
20
17
|
metaclass (0.0.1)
|
21
|
-
mocha (0.
|
18
|
+
mocha (0.13.3)
|
22
19
|
metaclass (~> 0.0.1)
|
23
|
-
multi_json (1.7.
|
24
|
-
nio4r (0.4.3-java)
|
20
|
+
multi_json (1.7.1)
|
25
21
|
rake (10.0.3)
|
26
22
|
resilient_socket (0.4.0)
|
27
23
|
semantic_logger
|
@@ -29,26 +25,26 @@ GEM
|
|
29
25
|
semantic_logger (2.0.0)
|
30
26
|
sync_attr
|
31
27
|
thread_safe
|
32
|
-
shoulda (3.
|
33
|
-
shoulda-context (~> 1.0.1)
|
34
|
-
shoulda-matchers (~> 1.4.1)
|
28
|
+
shoulda (3.4.0)
|
29
|
+
shoulda-context (~> 1.0, >= 1.0.1)
|
30
|
+
shoulda-matchers (~> 1.0, >= 1.4.1)
|
35
31
|
shoulda-context (1.0.2)
|
36
|
-
shoulda-matchers (1.4
|
32
|
+
shoulda-matchers (1.5.4)
|
37
33
|
activesupport (>= 3.0.0)
|
38
|
-
bourne (~> 1.
|
34
|
+
bourne (~> 1.3)
|
39
35
|
sync_attr (0.1.1)
|
40
36
|
thread_safe (0.1.0)
|
41
37
|
atomic
|
42
|
-
timers (1.1.0)
|
43
38
|
|
44
39
|
PLATFORMS
|
45
40
|
java
|
41
|
+
ruby
|
46
42
|
|
47
43
|
DEPENDENCIES
|
48
44
|
bson
|
49
45
|
bson_ext
|
50
|
-
celluloid-io (= 0.13.0.pre2)
|
51
46
|
gene_pool
|
47
|
+
mocha
|
52
48
|
multi_json
|
53
49
|
rake
|
54
50
|
resilient_socket
|
data/Rakefile
CHANGED
data/examples/e.rb
CHANGED
@@ -1,14 +0,0 @@
|
|
1
|
-
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
-
require 'rubygems'
|
3
|
-
require 'ruby_skynet'
|
4
|
-
require 'active_support/core_ext/hash/conversions'
|
5
|
-
|
6
|
-
SemanticLogger::Logger.default_level = :trace
|
7
|
-
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('skynet.log')
|
8
|
-
|
9
|
-
client = RubySkynet::Client.new('InquiriesService')
|
10
|
-
#h = {}; 1000.times{|i| h["hello#{i}"] = i}
|
11
|
-
#p client.call('echo', h)
|
12
|
-
data = Hash.from_xml(File.read("../../claritybase/test/load/inquiry.xml"))['inquiry']
|
13
|
-
p data
|
14
|
-
p client.call('create', data)
|
data/lib/ruby_skynet/client.rb
CHANGED
@@ -54,7 +54,7 @@ module RubySkynet
|
|
54
54
|
# Raises RubySkynet::SkynetException
|
55
55
|
def call(method_name, parameters, connection_params={})
|
56
56
|
# Skynet requires BSON RPC Calls to have the following format:
|
57
|
-
# https://github.com/
|
57
|
+
# https://github.com/skynetservices/skynet/blob/master/protocol.md
|
58
58
|
request_id = BSON::ObjectId.new.to_s
|
59
59
|
@logger.tagged request_id do
|
60
60
|
@logger.benchmark_info "Called Skynet Service: #{@service_name}.#{method_name}" do
|
data/lib/ruby_skynet/common.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
1
3
|
module RubySkynet
|
2
4
|
module Common
|
3
5
|
|
@@ -18,9 +20,15 @@ module RubySkynet
|
|
18
20
|
|
19
21
|
bytebuf.append!(bytes)
|
20
22
|
bytebuf.append!(socket.read(sz - 4))
|
21
|
-
raise "
|
23
|
+
raise "Socket is not returning #{sz} requested bytes. #{bytebuf.length} bytes returned" unless sz == bytebuf.length
|
22
24
|
return BSON.deserialize(bytebuf)
|
23
25
|
end
|
24
26
|
|
27
|
+
# Returns the local ip address being used by this machine to talk to the
|
28
|
+
# internet. By default connects to Google and determines what IP Address is used locally
|
29
|
+
def self.local_ip_address(remote_ip = '64.233.187.99')
|
30
|
+
@@local_ip_address ||= ::UDPSocket.open {|s| s.connect(remote_ip, 1); s.addr.last }
|
31
|
+
end
|
32
|
+
|
25
33
|
end
|
26
34
|
end
|
@@ -25,7 +25,7 @@ module RubySkynet
|
|
25
25
|
# Disable buffering the send since it is a RPC call
|
26
26
|
params[:buffered] = false
|
27
27
|
|
28
|
-
@logger.trace "Socket Connection parameters", params
|
28
|
+
@logger.trace "Socket Connection parameters", params.dup
|
29
29
|
|
30
30
|
# For each new connection
|
31
31
|
params[:on_connect] = Proc.new do |socket|
|
@@ -2,6 +2,7 @@ module RubySkynet
|
|
2
2
|
class Exception < ::RuntimeError; end
|
3
3
|
class ProtocolError < Exception; end
|
4
4
|
class ServiceException < Exception; end
|
5
|
+
class InvalidServiceException < ServiceException; end
|
5
6
|
class SkynetException < Exception; end
|
6
7
|
class ServiceUnavailable < SkynetException; end
|
7
8
|
end
|
data/lib/ruby_skynet/registry.rb
CHANGED
@@ -2,6 +2,7 @@ require 'sync_attr'
|
|
2
2
|
require 'multi_json'
|
3
3
|
require 'thread_safe'
|
4
4
|
require 'gene_pool'
|
5
|
+
require 'resolv'
|
5
6
|
|
6
7
|
#
|
7
8
|
# RubySkynet Registry Client
|
@@ -17,8 +18,8 @@ module RubySkynet
|
|
17
18
|
# Service Registry has the following format
|
18
19
|
# Key: [String] 'service_name/version/region'
|
19
20
|
# Value: [Array<String>] 'host:port', 'host:port'
|
20
|
-
|
21
|
-
|
21
|
+
sync_cattr_reader :service_registry do
|
22
|
+
start
|
22
23
|
end
|
23
24
|
|
24
25
|
@@on_server_removed_callbacks = ThreadSafe::Hash.new
|
@@ -61,24 +62,32 @@ module RubySkynet
|
|
61
62
|
}
|
62
63
|
end
|
63
64
|
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
65
|
+
# Register the supplied service at this Skynet Server host and Port
|
66
|
+
def self.register_service(name, version, region, hostname, port)
|
67
|
+
config = {
|
68
|
+
"Config" => {
|
69
|
+
"UUID" => "#{hostname}:#{port}-#{$$}-#{name}-#{version}",
|
70
|
+
"Name" => name,
|
71
|
+
"Version" => version.to_s,
|
72
|
+
"Region" => region,
|
73
|
+
"ServiceAddr" => {
|
74
|
+
"IPAddress" => hostname,
|
75
|
+
"Port" => port,
|
76
|
+
"MaxPort" => port + 999
|
77
|
+
},
|
78
|
+
},
|
79
|
+
"Registered" => true
|
80
|
+
}
|
81
|
+
doozer_pool.with_connection do |doozer|
|
82
|
+
doozer["/services/#{name}/#{version}/#{region}/#{hostname}/#{port}"] = MultiJson.encode(config)
|
76
83
|
end
|
77
84
|
end
|
78
85
|
|
79
|
-
#
|
80
|
-
|
81
|
-
|
86
|
+
# Deregister the supplied service from the Registry
|
87
|
+
def self.deregister_service(name, version, region, hostname, port)
|
88
|
+
doozer_pool.with_connection do |doozer|
|
89
|
+
doozer.delete("/services/#{name}/#{version}/#{region}/#{hostname}/#{port}") rescue nil
|
90
|
+
end
|
82
91
|
end
|
83
92
|
|
84
93
|
# Return a server that implements the specified service
|
@@ -115,35 +124,19 @@ module RubySkynet
|
|
115
124
|
end
|
116
125
|
|
117
126
|
# Returns [Array<String>] a list of servers implementing the requested service
|
118
|
-
def self.servers_for(service_name, version='*', region='Development'
|
119
|
-
if
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
127
|
+
def self.servers_for(service_name, version='*', region='Development')
|
128
|
+
if version == '*'
|
129
|
+
# Find the highest version for the named service in this region
|
130
|
+
version = -1
|
131
|
+
service_registry.keys.each do |key|
|
132
|
+
if match = key.match(/#{service_name}\/(\d+)\/#{region}/)
|
133
|
+
ver = match[1].to_i
|
134
|
+
version = ver if ver > version
|
144
135
|
end
|
145
136
|
end
|
146
|
-
|
137
|
+
end
|
138
|
+
if server_infos = service_registry["#{service_name}/#{version}/#{region}"]
|
139
|
+
server_infos.first.servers
|
147
140
|
end
|
148
141
|
end
|
149
142
|
|
@@ -155,83 +148,124 @@ module RubySkynet
|
|
155
148
|
(@@on_server_removed_callbacks[server] ||= ThreadSafe::Array.new) << block
|
156
149
|
end
|
157
150
|
|
151
|
+
IPV4_REG_EXP = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
|
152
|
+
|
153
|
+
# Returns [Integer] the score for the supplied ip_address
|
154
|
+
# Score currently ranges from 0 to 4 with 4 being the best score
|
155
|
+
# If the IP address does not match an IP v4 address a DNS lookup will
|
156
|
+
# be performed
|
157
|
+
def self.score_for_server(ip_address)
|
158
|
+
score = 0
|
159
|
+
# Each matching element adds 1 to the score
|
160
|
+
# 192.168. 0. 0
|
161
|
+
# 1
|
162
|
+
# 1
|
163
|
+
# 1
|
164
|
+
# 1
|
165
|
+
server_match = IPV4_REG_EXP.match(ip_address) || IPV4_REG_EXP.match(Resolv::DNS.new.getaddress(ip_address).to_s)
|
166
|
+
if server_match
|
167
|
+
@@local_match ||= IPV4_REG_EXP.match(Common.local_ip_address)
|
168
|
+
score = 0
|
169
|
+
(1..4).each do |i|
|
170
|
+
break if @@local_match[i].to_i != server_match[i].to_i
|
171
|
+
score += 1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
score
|
175
|
+
end
|
176
|
+
|
158
177
|
############################
|
159
|
-
|
178
|
+
protected
|
179
|
+
|
180
|
+
# Logging instance for this class
|
181
|
+
sync_cattr_reader :logger do
|
182
|
+
SemanticLogger::Logger.new(self, :debug)
|
183
|
+
end
|
160
184
|
|
161
|
-
#
|
185
|
+
# Lazy initialize Doozer Client Connection pool
|
186
|
+
sync_cattr_reader :doozer_pool do
|
187
|
+
GenePool.new(
|
188
|
+
:name =>"Doozer Connection Pool",
|
189
|
+
:pool_size => 5,
|
190
|
+
:timeout => 30,
|
191
|
+
:warn_timeout => 5,
|
192
|
+
:idle_timeout => 600,
|
193
|
+
:logger => logger,
|
194
|
+
:close_proc => :close
|
195
|
+
) do
|
196
|
+
Doozer::Client.new(doozer_config)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Fetch the all registry information from Doozer and sets the internal registry
|
162
201
|
# Also starts the monitoring thread to keep the registry up to date
|
163
|
-
def self.
|
202
|
+
def self.start
|
203
|
+
# Populate internal registry from doozer server
|
204
|
+
# Holds a lock in this process on the service_registry so that only
|
205
|
+
# this thread will pre-populate the local copy of the registry
|
164
206
|
registry = ThreadSafe::Hash.new
|
165
207
|
revision = nil
|
166
208
|
doozer_pool.with_connection do |doozer|
|
167
209
|
revision = doozer.current_revision
|
168
210
|
doozer.walk(DOOZER_SERVICES_PATH, revision).each do |node|
|
169
|
-
|
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
|
211
|
+
service_info_change(registry, node.path, node.value)
|
185
212
|
end
|
186
213
|
end
|
187
214
|
# Start monitoring thread to keep the registry up to date
|
188
|
-
@@monitor_thread = Thread.new {
|
215
|
+
@@monitor_thread = Thread.new { watch_registry(revision + 1) }
|
216
|
+
|
217
|
+
# Cleanup when process exits
|
218
|
+
at_exit do
|
219
|
+
if @@monitor_thread
|
220
|
+
@@monitor_thread.kill
|
221
|
+
@@monitor_thread.join
|
222
|
+
@@monitor_thread = nil
|
223
|
+
end
|
224
|
+
doozer_pool.close
|
225
|
+
end
|
189
226
|
registry
|
190
227
|
end
|
191
228
|
|
192
229
|
# Waits for any updates from Doozer and updates the internal service registry
|
193
|
-
def self.
|
230
|
+
def self.watch_registry(revision)
|
194
231
|
logger.info "Start monitoring #{DOOZER_SERVICES_PATH}"
|
195
232
|
# This thread must use its own dedicated doozer connection
|
196
233
|
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
234
|
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
235
|
+
# Watch for any changes
|
236
|
+
doozer.watch(DOOZER_SERVICES_PATH, revision) do |node|
|
237
|
+
service_info_change(service_registry, node.path, node.value)
|
238
|
+
logger.trace "Updated registry", service_registry
|
229
239
|
end
|
230
240
|
logger.info "Stopping monitoring thread normally"
|
231
241
|
rescue Exception => exc
|
232
242
|
logger.error "Exception in monitoring thread", exc
|
233
243
|
ensure
|
234
|
-
|
244
|
+
doozer.close if doozer
|
245
|
+
logger.info "Stopped monitoring for changes in the doozer registry"
|
246
|
+
end
|
247
|
+
|
248
|
+
# Service information changed in doozer, so update internal registry
|
249
|
+
def self.service_info_change(registry, path, value)
|
250
|
+
# path from doozer: "/services/TutorialService/1/Development/127.0.0.1/9000"
|
251
|
+
e = path.split('/')
|
252
|
+
|
253
|
+
# Key: [String] 'service_name/version/region'
|
254
|
+
key = "#{e[2]}/#{e[3]}/#{e[4]}"
|
255
|
+
hostname, port = e[5], e[6]
|
256
|
+
|
257
|
+
if value.strip.size > 0
|
258
|
+
entry = MultiJson.load(value)
|
259
|
+
if entry['Registered']
|
260
|
+
add_server(registry, key, hostname, port)
|
261
|
+
else
|
262
|
+
# Service just de-registered
|
263
|
+
remove_server(registry, key, hostname, port, false)
|
264
|
+
end
|
265
|
+
else
|
266
|
+
# Service has stopped and needs to be removed
|
267
|
+
remove_server(registry, key, hostname, port, true)
|
268
|
+
end
|
235
269
|
end
|
236
270
|
|
237
271
|
# Invoke any registered callbacks for the specific server
|
@@ -248,5 +282,94 @@ module RubySkynet
|
|
248
282
|
end
|
249
283
|
end
|
250
284
|
|
285
|
+
# :score: [Integer] Score
|
286
|
+
# :servers: [Array<String>] 'host:port', 'host:port'
|
287
|
+
ServerInfo = Struct.new(:score, :servers )
|
288
|
+
|
289
|
+
# Format of the internal services registry
|
290
|
+
# key: [String] "<service_name>/<version>/<region>"
|
291
|
+
# value: [ServiceInfo, ServiceInfo]
|
292
|
+
# Sorted by highest score first
|
293
|
+
|
294
|
+
# Add the host to the registry based on it's score
|
295
|
+
def self.add_server(registry, key, hostname, port)
|
296
|
+
server = "#{hostname}:#{port}"
|
297
|
+
logger.debug "#monitor Add/Update Service: #{key} => #{server.inspect}"
|
298
|
+
|
299
|
+
server_infos = (registry[key] ||= ThreadSafe::Array.new)
|
300
|
+
|
301
|
+
# If already present, then nothing to do
|
302
|
+
server_info = server_infos.find{|si| si.server == server}
|
303
|
+
return server_info if server_info
|
304
|
+
|
305
|
+
# Look for the same score with a different server
|
306
|
+
score = score_for_server(hostname)
|
307
|
+
if server_info = server_infos.find{|si| si.score == score}
|
308
|
+
server_info.servers << server
|
309
|
+
return server_info
|
310
|
+
end
|
311
|
+
|
312
|
+
# New score
|
313
|
+
servers = ThreadSafe::Array.new
|
314
|
+
servers << server
|
315
|
+
server_info = ServerInfo.new(score, servers)
|
316
|
+
|
317
|
+
# Insert into Array in order of score
|
318
|
+
if index = server_infos.find_index {|si| si.score <= score}
|
319
|
+
server_infos.insert(index, server_info)
|
320
|
+
else
|
321
|
+
server_infos << server_info
|
322
|
+
end
|
323
|
+
server_info
|
324
|
+
end
|
325
|
+
|
326
|
+
# Remove the host from the registry based
|
327
|
+
# Returns the server instance if it was removed
|
328
|
+
def self.remove_server(registry, key, hostname, port, notify)
|
329
|
+
server = "#{hostname}:#{port}"
|
330
|
+
logger.debug "Remove Service: #{key} => #{server.inspect}"
|
331
|
+
server_info = nil
|
332
|
+
if server_infos = registry[key]
|
333
|
+
server_infos.each do |si|
|
334
|
+
if si.servers.delete(server)
|
335
|
+
server_info = si
|
336
|
+
break
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# Found server
|
341
|
+
if server_info
|
342
|
+
# Cleanup if no more servers in server list
|
343
|
+
server_infos.delete(server_info) if server_info.servers.size == 0
|
344
|
+
|
345
|
+
# Cleanup if no more server infos
|
346
|
+
registry.delete(key) if server_infos.size == 0
|
347
|
+
|
348
|
+
server_removed(server) if notify
|
349
|
+
end
|
350
|
+
end
|
351
|
+
server_info
|
352
|
+
end
|
353
|
+
|
354
|
+
# Check doozer for servers matching supplied criteria
|
355
|
+
# Code unused, consider deleting
|
356
|
+
def self.remote_servers_for(service_name, version='*', region='Development')
|
357
|
+
if version != '*'
|
358
|
+
registered_implementers(service_name, version, region).map do |host|
|
359
|
+
service = host['Config']['ServiceAddr']
|
360
|
+
"#{service['IPAddress']}:#{service['Port']}"
|
361
|
+
end
|
362
|
+
else
|
363
|
+
# Find the highest version of any particular service
|
364
|
+
versions = {}
|
365
|
+
registered_implementers(service_name, version, region).each do |host|
|
366
|
+
service = host['Config']['ServiceAddr']
|
367
|
+
(versions[version.to_i] ||= []) << "#{service['IPAddress']}:#{service['Port']}"
|
368
|
+
end
|
369
|
+
# Return the servers implementing the highest version number
|
370
|
+
versions.sort.last.last
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
251
374
|
end
|
252
375
|
end
|
data/lib/ruby_skynet/server.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
1
|
require 'bson'
|
2
|
-
require 'celluloid/io'
|
3
|
-
|
4
|
-
# Replace Celluloid logger immediately upon loading the Server Instance
|
5
|
-
Celluloid.logger = SemanticLogger::Logger.new('Celluloid')
|
6
2
|
|
7
3
|
#
|
8
4
|
# RubySkynet Server
|
@@ -11,92 +7,80 @@ Celluloid.logger = SemanticLogger::Logger.new('Celluloid')
|
|
11
7
|
#
|
12
8
|
module RubySkynet
|
13
9
|
class Server
|
14
|
-
include Celluloid::IO
|
15
10
|
include SemanticLogger::Loggable
|
16
11
|
|
17
|
-
# TODO Make Server instance based rather than class based. Then make instance global
|
18
|
-
@@hostname = nil
|
19
|
-
@@port = 2000
|
20
|
-
@@region = 'Development'
|
21
12
|
@@server = nil
|
13
|
+
@@services = ThreadSafe::Hash.new
|
22
14
|
|
23
|
-
|
24
|
-
|
15
|
+
# Start a single instance of the server
|
16
|
+
def self.start(region = 'Development', start_port = 2000, hostname = nil)
|
17
|
+
@@server ||= new(region, start_port, hostname)
|
25
18
|
end
|
26
19
|
|
20
|
+
# Stop the single instance of the server
|
27
21
|
def self.stop
|
28
|
-
@@server.
|
22
|
+
@@server.finalize if @@server
|
29
23
|
@@server = nil
|
30
24
|
end
|
31
25
|
|
26
|
+
# Is the single instance of the server running
|
32
27
|
def self.running?
|
33
28
|
(@@server != nil) && @@server.running?
|
34
29
|
end
|
35
30
|
|
36
|
-
# Region under which to register Skynet services
|
37
|
-
# Default: 'Development'
|
38
|
-
def self.region
|
39
|
-
@@region
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.region=(region)
|
43
|
-
@@region = region
|
44
|
-
end
|
45
|
-
|
46
|
-
# Port to listen to requests on
|
47
|
-
# Default: 2000
|
48
|
-
def self.port
|
49
|
-
@@port
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.port=(port)
|
53
|
-
@@port = port
|
54
|
-
end
|
55
|
-
|
56
|
-
# Override the hostname at which this server is running
|
57
|
-
# Useful when the service is behind a firewall or NAT device
|
58
|
-
def self.hostname=(hostname)
|
59
|
-
@@hostname = hostname
|
60
|
-
end
|
61
|
-
|
62
|
-
# Returns [String] hostname of the current server
|
63
|
-
def self.hostname
|
64
|
-
@@hostname ||= Socket.gethostname
|
65
|
-
end
|
66
|
-
|
67
|
-
@@services = ThreadSafe::Hash.new
|
68
|
-
|
69
31
|
# Services currently loaded and available at this server when running
|
70
32
|
def self.services
|
71
33
|
@@services
|
72
34
|
end
|
73
35
|
|
74
|
-
# Registers a Service Class as being available at this port
|
36
|
+
# Registers a Service Class as being available at this host and port
|
75
37
|
def self.register_service(klass)
|
76
|
-
|
38
|
+
raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:service_name) && klass.respond_to?(:service_version)
|
39
|
+
|
40
|
+
if previous_klass = @@services[klass.service_name] && (previous_klass.name != klass.name)
|
41
|
+
logger.warn("Service with name: #{klass.service_name} is already registered to a different implementation:#{previous_klass.name}")
|
42
|
+
end
|
77
43
|
@@services[klass.service_name] = klass
|
78
|
-
|
44
|
+
@@server.register_service(klass) if @@server
|
79
45
|
end
|
80
46
|
|
81
|
-
# De-register service
|
47
|
+
# De-register service
|
82
48
|
def self.deregister_service(klass)
|
83
|
-
RubySkynet::
|
84
|
-
|
85
|
-
|
49
|
+
raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:service_name) && klass.respond_to?(:service_version)
|
50
|
+
|
51
|
+
@@server.deregister_service(klass) if @@server
|
86
52
|
@@services.delete(klass.service_name)
|
87
53
|
end
|
88
54
|
|
55
|
+
# The actual port the server is running at which will be different
|
56
|
+
# from Server.port if that port was already in use at startup
|
57
|
+
attr_reader :hostname, :port, :region
|
58
|
+
|
89
59
|
# Start the server so that it can start taking RPC calls
|
90
60
|
# Returns false if the server is already running
|
91
|
-
def initialize(
|
92
|
-
|
93
|
-
|
94
|
-
#
|
95
|
-
|
96
|
-
|
61
|
+
def initialize(region = 'Development', start_port = 2000, hostname = nil)
|
62
|
+
hostname ||= Common.local_ip_address
|
63
|
+
|
64
|
+
# If port is in use, try the next port in sequence
|
65
|
+
port_count = 0
|
66
|
+
begin
|
67
|
+
@server = ::TCPServer.new(hostname, start_port + port_count)
|
68
|
+
@hostname = hostname
|
69
|
+
@port = start_port + port_count
|
70
|
+
@region = region
|
71
|
+
rescue Errno::EADDRINUSE => exc
|
72
|
+
if port_count < 999
|
73
|
+
port_count += 1
|
74
|
+
retry
|
75
|
+
end
|
76
|
+
raise exc
|
77
|
+
end
|
78
|
+
|
79
|
+
# Start Server listener thread
|
80
|
+
Thread.new { run }
|
97
81
|
|
98
82
|
# Register services hosted by this server
|
99
|
-
self.class.services.
|
83
|
+
self.class.services.each_value {|klass| register_service(klass)}
|
100
84
|
end
|
101
85
|
|
102
86
|
def finalize
|
@@ -104,21 +88,25 @@ module RubySkynet
|
|
104
88
|
logger.info "Skynet Server Stopped"
|
105
89
|
|
106
90
|
# Deregister services hosted by this server
|
107
|
-
|
108
|
-
|
109
|
-
doozer.delete(klass.service_key) rescue nil
|
110
|
-
end
|
91
|
+
self.class.services.each_value do |klass|
|
92
|
+
deregister_service(klass) rescue nil
|
111
93
|
end
|
112
|
-
logger.info "Skynet Services De-registered
|
94
|
+
logger.info "Skynet Services De-registered"
|
113
95
|
end
|
114
96
|
|
115
97
|
def run
|
116
|
-
logger.info("Starting listener on #{
|
98
|
+
logger.info("Starting listener on #{hostname}:#{port}")
|
117
99
|
loop do
|
118
100
|
logger.debug "Waiting for a client to connect"
|
119
101
|
begin
|
120
|
-
|
121
|
-
|
102
|
+
client = @server.accept
|
103
|
+
# We could use a thread pool here, but JRuby already does that
|
104
|
+
# and MRI threads are very light weight
|
105
|
+
Thread.new { handle_connection(client) }
|
106
|
+
rescue Errno::EBADF, IOError => exc
|
107
|
+
logger.info "TCPServer listener thread shutting down. #{exc.class}: #{exc.message}"
|
108
|
+
return
|
109
|
+
rescue ScriptError, NameError, StandardError, Exception => exc
|
122
110
|
logger.error "Exception while processing connection request", exc
|
123
111
|
end
|
124
112
|
end
|
@@ -152,11 +140,11 @@ module RubySkynet
|
|
152
140
|
break unless request
|
153
141
|
params = BSON.deserialize(request['in'])
|
154
142
|
logger.trace 'Parameters', params
|
143
|
+
|
155
144
|
reply = begin
|
156
145
|
on_message(service_name, request['method'].to_sym, params)
|
157
|
-
rescue Exception => exc
|
146
|
+
rescue ScriptError, NameError, StandardError, Exception => exc
|
158
147
|
logger.error "Exception while calling service: #{service_name}", exc
|
159
|
-
# TODO Return exception in header
|
160
148
|
{ :exception => {:message => exc.message, :class => exc.class.name} }
|
161
149
|
end
|
162
150
|
|
@@ -173,6 +161,9 @@ module RubySkynet
|
|
173
161
|
break
|
174
162
|
end
|
175
163
|
end
|
164
|
+
rescue ScriptError, NameError, StandardError, Exception => exc
|
165
|
+
logger.error "#handle_connection Exception", exc
|
166
|
+
ensure
|
176
167
|
# Disconnect from the client
|
177
168
|
client.close
|
178
169
|
logger.debug "Disconnected from the client"
|
@@ -186,37 +177,28 @@ module RubySkynet
|
|
186
177
|
############################################################################
|
187
178
|
protected
|
188
179
|
|
189
|
-
#
|
190
|
-
def
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
"Port" => Server.port,
|
200
|
-
"MaxPort" => Server.port + 999
|
201
|
-
},
|
202
|
-
},
|
203
|
-
"Registered" => true
|
204
|
-
}
|
205
|
-
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
206
|
-
doozer[klass.service_key] = MultiJson.encode(config)
|
207
|
-
end
|
180
|
+
# Registers a Service Class as being available at this server
|
181
|
+
def register_service(klass)
|
182
|
+
logger.debug "Registering Service: #{klass.name} with name: #{klass.service_name}"
|
183
|
+
Registry.register_service(klass.service_name, klass.service_version, @region, @hostname, @port)
|
184
|
+
end
|
185
|
+
|
186
|
+
# De-register service from this server
|
187
|
+
def deregister_service(klass)
|
188
|
+
logger.debug "De-registering Service: #{klass.name} with name: #{klass.service_name}"
|
189
|
+
Registry.deregister_service(klass.service_name, klass.service_version, @region, @hostname, @port)
|
208
190
|
end
|
209
191
|
|
210
192
|
# Called for each message received from the client
|
211
193
|
# Returns a Hash that is sent back to the caller
|
212
194
|
def on_message(service_name, method, params)
|
213
|
-
logger.
|
195
|
+
logger.benchmark_info("Skynet Call: #{service_name}##{method}") do
|
214
196
|
logger.trace "Method Call: #{method} with parameters:", params
|
215
197
|
klass = Server.services[service_name]
|
216
198
|
raise "Invalid Skynet RPC call, service: #{service_name} is not available at this server" unless klass
|
199
|
+
# TODO Use pool of services
|
217
200
|
service = klass.new
|
218
201
|
raise "Invalid Skynet RPC call, method: #{method} does not exist for service: #{service_name}" unless service.respond_to?(method)
|
219
|
-
# TODO Use pool of services, or Celluloid here
|
220
202
|
service.send(method, params)
|
221
203
|
end
|
222
204
|
end
|
data/lib/ruby_skynet/service.rb
CHANGED
@@ -29,27 +29,23 @@ module RubySkynet
|
|
29
29
|
# Name of this service to Register with Skynet
|
30
30
|
# Default: class name
|
31
31
|
def service_name
|
32
|
-
|
32
|
+
@service_name ||= name.gsub('::', '.')
|
33
33
|
end
|
34
34
|
|
35
35
|
def service_name=(service_name)
|
36
|
-
|
36
|
+
@service_name = service_name
|
37
37
|
end
|
38
38
|
|
39
39
|
# Version of this service to register with Skynet, defaults to 1
|
40
40
|
# Default: 1
|
41
41
|
def service_version
|
42
|
-
|
42
|
+
@service_version ||= 1
|
43
43
|
end
|
44
44
|
|
45
45
|
def service_version=(service_version)
|
46
|
-
|
46
|
+
@service_version = service_version
|
47
47
|
end
|
48
48
|
|
49
|
-
# Key by which this service is known in the doozer registry
|
50
|
-
def service_key
|
51
|
-
"/services/#{service_name}/#{service_version}/#{Server.region}/#{Server.hostname}/#{Server.port}"
|
52
|
-
end
|
53
49
|
end
|
54
50
|
|
55
51
|
end
|
data/lib/ruby_skynet/version.rb
CHANGED
@@ -6,8 +6,6 @@ require 'rubygems'
|
|
6
6
|
require 'test/unit'
|
7
7
|
require 'shoulda'
|
8
8
|
require 'ruby_skynet'
|
9
|
-
require 'simple_server'
|
10
|
-
require 'multi_json'
|
11
9
|
|
12
10
|
# Register an appender if one is not already registered
|
13
11
|
if SemanticLogger::Logger.appenders.size == 0
|
@@ -15,8 +13,33 @@ if SemanticLogger::Logger.appenders.size == 0
|
|
15
13
|
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
|
16
14
|
end
|
17
15
|
|
16
|
+
class ClientTestService
|
17
|
+
include RubySkynet::Service
|
18
|
+
|
19
|
+
# Methods implemented by this service
|
20
|
+
# Must take a Hash as input
|
21
|
+
# Must Return a Hash response or nil for no response
|
22
|
+
def test1(params)
|
23
|
+
{ 'result' => 'test1' }
|
24
|
+
end
|
25
|
+
|
26
|
+
def sleep(params)
|
27
|
+
Kernel.sleep params['duration'] || 1
|
28
|
+
{ 'result' => 'sleep' }
|
29
|
+
end
|
30
|
+
|
31
|
+
def fail(params)
|
32
|
+
if params['attempt'].to_i >= 2
|
33
|
+
{ 'result' => 'fail' }
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
18
41
|
# Unit Test for ResilientSocket::TCPClient
|
19
|
-
class
|
42
|
+
class ClientTest < Test::Unit::TestCase
|
20
43
|
context RubySkynet::Client do
|
21
44
|
|
22
45
|
context "without server" do
|
@@ -32,41 +55,17 @@ class RubySkynetClientTest < Test::Unit::TestCase
|
|
32
55
|
|
33
56
|
context "with server" do
|
34
57
|
setup do
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@server = SimpleServer.new(@port)
|
38
|
-
@server_name = "localhost:#{@port}"
|
58
|
+
@region = 'ClientTest'
|
59
|
+
RubySkynet::Server.start(@region)
|
39
60
|
|
40
|
-
|
41
|
-
@service_name = "TestService"
|
61
|
+
@service_name = 'ClientTestService'
|
42
62
|
@version = 1
|
43
|
-
|
44
|
-
@
|
45
|
-
config = {
|
46
|
-
"Config" => {
|
47
|
-
"UUID" => "3978b371-15e9-40f8-9b7b-59ae88d8c7ec",
|
48
|
-
"Name" => @service_name,
|
49
|
-
"Version" => @version.to_s,
|
50
|
-
"Region" => @region,
|
51
|
-
"ServiceAddr" => {
|
52
|
-
"IPAddress" => @ip_address,
|
53
|
-
"Port" => @port,
|
54
|
-
"MaxPort" => @port + 999
|
55
|
-
},
|
56
|
-
},
|
57
|
-
"Registered" => true
|
58
|
-
}
|
59
|
-
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
60
|
-
doozer["/services/#{@service_name}/#{@version}/#{@region}/#{@ip_address}/#{@port}"] = MultiJson.encode(config)
|
61
|
-
end
|
63
|
+
|
64
|
+
@read_timeout = 3.0
|
62
65
|
end
|
63
66
|
|
64
67
|
teardown do
|
65
|
-
|
66
|
-
# De-register server in doozer
|
67
|
-
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
68
|
-
doozer.delete("/services/#{@service_name}/#{@version}/#{@region}/#{@ip_address}/#{@port}") rescue nil
|
69
|
-
end
|
68
|
+
RubySkynet::Server.stop
|
70
69
|
end
|
71
70
|
|
72
71
|
context "with client connection" do
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Allow test to be run in-place without requiring a gem install
|
2
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'test/unit'
|
6
|
+
require 'shoulda'
|
7
|
+
require 'mocha/setup'
|
8
|
+
require 'ruby_skynet'
|
9
|
+
|
10
|
+
# Register an appender if one is not already registered
|
11
|
+
if SemanticLogger::Logger.appenders.size == 0
|
12
|
+
SemanticLogger::Logger.default_level = :trace
|
13
|
+
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
|
14
|
+
end
|
15
|
+
|
16
|
+
# Unit Test
|
17
|
+
class RegistryTest < Test::Unit::TestCase
|
18
|
+
context 'RubySkynet::Service' do
|
19
|
+
|
20
|
+
setup do
|
21
|
+
@service_name = 'MyRegistryService'
|
22
|
+
@version = 5
|
23
|
+
@region = 'RegistryTest'
|
24
|
+
@hostname = '127.0.0.1'
|
25
|
+
@port = 2100
|
26
|
+
@service_key = "/services/#{@service_name}/#{@version}/#{@region}/#{@hostname}/#{@port}"
|
27
|
+
end
|
28
|
+
|
29
|
+
context "without a registered service" do
|
30
|
+
should "not be in doozer" do
|
31
|
+
RubySkynet::Registry.send(:doozer_pool).with_connection do |doozer|
|
32
|
+
assert_equal '', doozer[@service_key]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with a registered service" do
|
38
|
+
setup do
|
39
|
+
RubySkynet::Registry.register_service(@service_name, @version, @region, @hostname, @port)
|
40
|
+
# Allow time for doozer callback that service was registered
|
41
|
+
sleep 0.1
|
42
|
+
end
|
43
|
+
|
44
|
+
teardown do
|
45
|
+
RubySkynet::Registry.deregister_service(@service_name, @version, @region, @hostname, @port)
|
46
|
+
# Allow time for doozer callback that service was deregistered
|
47
|
+
sleep 0.1
|
48
|
+
# No servers should be in the local registry
|
49
|
+
assert_equal nil, RubySkynet::Registry.servers_for(@service_name, @version, @region)
|
50
|
+
end
|
51
|
+
|
52
|
+
should "find server using exact match" do
|
53
|
+
assert servers = RubySkynet::Registry.servers_for(@service_name, @version, @region)
|
54
|
+
assert_equal 1, servers.size
|
55
|
+
assert_equal "#{@hostname}:#{@port}", servers.first
|
56
|
+
end
|
57
|
+
|
58
|
+
should "find server using * version match" do
|
59
|
+
assert servers = RubySkynet::Registry.servers_for(@service_name, '*', @region)
|
60
|
+
assert_equal 1, servers.size
|
61
|
+
assert_equal "#{@hostname}:#{@port}", servers.first
|
62
|
+
end
|
63
|
+
|
64
|
+
should "return nil when service not found" do
|
65
|
+
assert_equal nil, RubySkynet::Registry.servers_for('MissingService', @version, @region)
|
66
|
+
end
|
67
|
+
|
68
|
+
should "return nil when version not found" do
|
69
|
+
assert_equal nil, RubySkynet::Registry.servers_for(@service_name, @version+1, @region)
|
70
|
+
end
|
71
|
+
|
72
|
+
should "return nil when region not found" do
|
73
|
+
assert_equal nil, RubySkynet::Registry.servers_for(@service_name, @version, 'OtherRegion')
|
74
|
+
end
|
75
|
+
|
76
|
+
should "be in doozer" do
|
77
|
+
RubySkynet::Registry.send(:doozer_pool).with_connection do |doozer|
|
78
|
+
assert_equal true, doozer[@service_key].length > 20
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "scoring" do
|
84
|
+
[
|
85
|
+
['192.168.11.0', 4 ],
|
86
|
+
['192.168.11.10', 3 ],
|
87
|
+
['192.168.10.0', 2 ],
|
88
|
+
['192.5.10.0', 1 ],
|
89
|
+
['10.0.11.0', 0 ],
|
90
|
+
].each do |test|
|
91
|
+
should "handle score #{test[1]}" do
|
92
|
+
RubySkynet::Common.stubs(:local_ip_address).returns("192.168.11.0")
|
93
|
+
assert_equal test[1], RubySkynet::Registry.score_for_server(test[0]), "Local: #{RubySkynet::Common.local_ip_address} Server: #{test[0]} Score: #{test[1]}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -12,10 +12,6 @@ if SemanticLogger::Logger.appenders.size == 0
|
|
12
12
|
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
|
13
13
|
end
|
14
14
|
|
15
|
-
RubySkynet::Server.port = 2100
|
16
|
-
RubySkynet::Server.region = 'Test'
|
17
|
-
RubySkynet::Server.hostname = '127.0.0.1'
|
18
|
-
|
19
15
|
class TestService
|
20
16
|
include RubySkynet::Service
|
21
17
|
|
@@ -25,38 +21,34 @@ class TestService
|
|
25
21
|
def echo(params)
|
26
22
|
params
|
27
23
|
end
|
24
|
+
|
25
|
+
def exception_test(params)
|
26
|
+
raise "Exception message"
|
27
|
+
end
|
28
28
|
end
|
29
29
|
|
30
30
|
# Unit Test for ResilientSocket::TCPClient
|
31
|
-
class
|
31
|
+
class ServiceTest < Test::Unit::TestCase
|
32
32
|
context 'RubySkynet::Service' do
|
33
33
|
context "with server" do
|
34
34
|
setup do
|
35
|
-
|
35
|
+
@region = 'Test'
|
36
36
|
@service_name = 'TestService'
|
37
37
|
@version = 1
|
38
|
-
@region
|
39
|
-
|
38
|
+
RubySkynet::Server.start(@region)
|
39
|
+
sleep 0.2
|
40
40
|
end
|
41
41
|
|
42
42
|
teardown do
|
43
|
-
|
44
|
-
|
45
|
-
rescue Celluloid::DeadActorError
|
46
|
-
end
|
43
|
+
RubySkynet::Server.stop
|
44
|
+
SemanticLogger::Logger.flush
|
47
45
|
end
|
48
46
|
|
49
|
-
should "
|
50
|
-
assert_equal
|
47
|
+
should "be running" do
|
48
|
+
assert_equal true, RubySkynet::Server.running?
|
51
49
|
end
|
52
50
|
|
53
|
-
|
54
|
-
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
55
|
-
assert_equal true, doozer[@doozer_key].length > 20
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
context "calling with a client" do
|
51
|
+
context "using a client" do
|
60
52
|
setup do
|
61
53
|
@client = RubySkynet::Client.new(@service_name, @version, @region)
|
62
54
|
end
|
@@ -66,6 +58,13 @@ class RubySkynetServiceTest < Test::Unit::TestCase
|
|
66
58
|
assert_equal 'some', reply.keys.first
|
67
59
|
assert_equal 'parameters', reply.values.first
|
68
60
|
end
|
61
|
+
|
62
|
+
# Cellulloid 0.13.0.pre2 is doing something weird here and preventing the
|
63
|
+
# Server from receiving the exception
|
64
|
+
should "handle service exceptions" do
|
65
|
+
reply = @client.call(:exception_test, 'some' => 'parameters')
|
66
|
+
assert_equal "Exception message", reply['exception']['message']
|
67
|
+
end
|
69
68
|
end
|
70
69
|
|
71
70
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_skynet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.0
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-03-
|
11
|
+
date: 2013-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: semantic_logger
|
@@ -80,20 +80,6 @@ dependencies:
|
|
80
80
|
- - '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: celluloid-io
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - '>='
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 0.13.0.pre2
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - '>='
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 0.13.0.pre2
|
97
83
|
description: Ruby Client for invoking Skynet services
|
98
84
|
email:
|
99
85
|
- reidmo@gmail.com
|
@@ -122,10 +108,10 @@ files:
|
|
122
108
|
- lib/ruby_skynet/service.rb
|
123
109
|
- lib/ruby_skynet/version.rb
|
124
110
|
- test.sh
|
111
|
+
- test/client_test.rb
|
125
112
|
- test/doozer_client_test.rb
|
126
|
-
- test/
|
127
|
-
- test/
|
128
|
-
- test/simple_server.rb
|
113
|
+
- test/registry_test.rb
|
114
|
+
- test/service_test.rb
|
129
115
|
homepage: https://github.com/ClarityServices/ruby_skynet
|
130
116
|
licenses:
|
131
117
|
- Apache License V2.0
|
@@ -141,9 +127,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
127
|
version: '0'
|
142
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
129
|
requirements:
|
144
|
-
- - '
|
130
|
+
- - '>='
|
145
131
|
- !ruby/object:Gem::Version
|
146
|
-
version:
|
132
|
+
version: '0'
|
147
133
|
requirements: []
|
148
134
|
rubyforge_project:
|
149
135
|
rubygems_version: 2.0.2
|
data/test/simple_server.rb
DELETED
@@ -1,122 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'socket'
|
3
|
-
require 'bson'
|
4
|
-
require 'semantic_logger'
|
5
|
-
require 'celluloid/io'
|
6
|
-
|
7
|
-
# This a simple stand-alone server that does not use the Skynet code so that
|
8
|
-
# the Skynet code can be tested
|
9
|
-
|
10
|
-
# Simple single threaded server for testing purposes using a local socket
|
11
|
-
# Sends and receives BSON Messages
|
12
|
-
class SimpleServer
|
13
|
-
include Celluloid::IO
|
14
|
-
|
15
|
-
def initialize(port)
|
16
|
-
# Since we included Celluloid::IO, we're actually making a
|
17
|
-
# Celluloid::IO::TCPServer here
|
18
|
-
@server = TCPServer.new('127.0.0.1', port)
|
19
|
-
@logger = SemanticLogger::Logger.new(self.class)
|
20
|
-
async.run
|
21
|
-
end
|
22
|
-
|
23
|
-
def run
|
24
|
-
loop do
|
25
|
-
@logger.debug "Waiting for a client to connect"
|
26
|
-
async.handle_connection(@server.accept)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def finalize
|
31
|
-
@server.close if @server
|
32
|
-
end
|
33
|
-
|
34
|
-
# Called for each message received from the client
|
35
|
-
# Returns a Hash that is sent back to the caller
|
36
|
-
def on_message(method, params)
|
37
|
-
case method
|
38
|
-
when 'test1'
|
39
|
-
{ 'result' => 'test1' }
|
40
|
-
when 'sleep'
|
41
|
-
sleep params['duration'] || 1
|
42
|
-
{ 'result' => 'sleep' }
|
43
|
-
when 'fail'
|
44
|
-
if params['attempt'].to_i >= 2
|
45
|
-
{ 'result' => 'fail' }
|
46
|
-
else
|
47
|
-
nil
|
48
|
-
end
|
49
|
-
else
|
50
|
-
{ 'result' => "Unknown method: #{method}" }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Called for each client connection
|
55
|
-
def handle_connection(client)
|
56
|
-
@logger.debug "Client connected, waiting for data from client"
|
57
|
-
|
58
|
-
# Process handshake
|
59
|
-
handshake = {
|
60
|
-
'registered' => true,
|
61
|
-
'clientid' => '123'
|
62
|
-
}
|
63
|
-
client.write(BSON.serialize(handshake).to_s)
|
64
|
-
read_bson_document(client)
|
65
|
-
|
66
|
-
while(header = read_bson_document(client)) do
|
67
|
-
@logger.debug "\n******************"
|
68
|
-
@logger.debug "Received Request"
|
69
|
-
@logger.trace 'Header', header
|
70
|
-
|
71
|
-
request = read_bson_document(client)
|
72
|
-
@logger.trace 'Request', request
|
73
|
-
break unless request
|
74
|
-
|
75
|
-
if reply = on_message(request['method'], BSON.deserialize(request['in']))
|
76
|
-
@logger.debug "Sending Header"
|
77
|
-
# For this test we just send back the received header
|
78
|
-
client.write(BSON.serialize(header).to_s)
|
79
|
-
|
80
|
-
@logger.debug "Sending Reply"
|
81
|
-
@logger.trace 'Reply', reply
|
82
|
-
client.write(BSON.serialize({'out' => BSON.serialize(reply).to_s}).to_s)
|
83
|
-
else
|
84
|
-
@logger.debug "Closing client since no reply is being sent back"
|
85
|
-
@server.close
|
86
|
-
client.close
|
87
|
-
@logger.debug "Server closed"
|
88
|
-
async.run
|
89
|
-
@logger.debug "Server Restarted"
|
90
|
-
break
|
91
|
-
end
|
92
|
-
end
|
93
|
-
# Disconnect from the client
|
94
|
-
client.close
|
95
|
-
@logger.debug "Disconnected from the client"
|
96
|
-
end
|
97
|
-
|
98
|
-
# Read the bson document, returning nil if the IO is closed
|
99
|
-
# before receiving any data or a complete BSON document
|
100
|
-
def read_bson_document(io)
|
101
|
-
bytebuf = BSON::ByteBuffer.new
|
102
|
-
# Read 4 byte size of following BSON document
|
103
|
-
bytes = io.read(4)
|
104
|
-
return unless bytes
|
105
|
-
# Read BSON document
|
106
|
-
sz = bytes.unpack("V")[0]
|
107
|
-
bytebuf.append!(bytes)
|
108
|
-
bytes = io.read(sz-4)
|
109
|
-
return unless bytes
|
110
|
-
bytebuf.append!(bytes)
|
111
|
-
return BSON.deserialize(bytebuf)
|
112
|
-
end
|
113
|
-
|
114
|
-
end
|
115
|
-
|
116
|
-
if $0 == __FILE__
|
117
|
-
SemanticLogger::Logger.default_level = :trace
|
118
|
-
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(STDOUT)
|
119
|
-
Celluloid.logger = SemanticLogger::Logger.new('Celluloid')
|
120
|
-
server = SimpleServer.new(2000)
|
121
|
-
server.thread.join
|
122
|
-
end
|