ruby_skynet 0.4.0.pre2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|