ruby_skynet 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -1
- data/Gemfile.lock +36 -12
- data/README.md +53 -17
- data/Rakefile +1 -0
- data/lib/ruby_skynet.rb +4 -3
- data/lib/ruby_skynet/client.rb +0 -2
- data/lib/ruby_skynet/common.rb +22 -0
- data/lib/ruby_skynet/connection.rb +6 -22
- data/lib/ruby_skynet/server.rb +235 -0
- data/lib/ruby_skynet/service.rb +58 -0
- data/lib/ruby_skynet/version.rb +1 -1
- data/test.log +0 -0
- data/test/doozer_client_test.rb +0 -3
- data/test/ruby_skynet_client_test.rb +6 -3
- data/test/ruby_skynet_service_test.rb +73 -0
- data/test/simple_server.rb +20 -33
- metadata +12 -9
- data/dev.log +0 -0
data/Gemfile
CHANGED
@@ -4,6 +4,7 @@ group :test do
|
|
4
4
|
gem "shoulda"
|
5
5
|
end
|
6
6
|
|
7
|
+
gem "rake"
|
7
8
|
gem "semantic_logger"
|
8
9
|
gem "resilient_socket"
|
9
10
|
# Thread Safe Hash and Array
|
@@ -14,4 +15,7 @@ gem "multi_json"
|
|
14
15
|
gem "ruby_protobuf"
|
15
16
|
# Wire format when communicating with services
|
16
17
|
gem "bson"
|
17
|
-
gem "bson_ext", :platform => :ruby
|
18
|
+
gem "bson_ext", :platform => :ruby
|
19
|
+
# Celluloid::IO is used to create SkyNet services in Ruby
|
20
|
+
# multi_json?
|
21
|
+
gem "celluloid-io"
|
data/Gemfile.lock
CHANGED
@@ -1,26 +1,46 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activesupport (3.2.
|
4
|
+
activesupport (3.2.9)
|
5
5
|
i18n (~> 0.6)
|
6
6
|
multi_json (~> 1.0)
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
bourne (1.1.2)
|
8
|
+
mocha (= 0.10.5)
|
9
|
+
bson (1.8.0)
|
10
|
+
activesupport
|
11
|
+
bson_ext (1.8.0)
|
12
|
+
bson (~> 1.8.0)
|
13
|
+
celluloid (0.12.3)
|
14
|
+
facter (>= 1.6.12)
|
15
|
+
timers (>= 1.0.0)
|
16
|
+
celluloid-io (0.12.0)
|
17
|
+
celluloid (~> 0.12.0)
|
18
|
+
nio4r (>= 0.4.0)
|
19
|
+
facter (1.6.16)
|
20
|
+
gene_pool (1.3.0)
|
10
21
|
i18n (0.6.1)
|
11
|
-
|
12
|
-
|
22
|
+
metaclass (0.0.1)
|
23
|
+
mocha (0.10.5)
|
24
|
+
metaclass (~> 0.0.1)
|
25
|
+
multi_json (1.4.0)
|
26
|
+
nio4r (0.4.3)
|
27
|
+
rake (10.0.2)
|
28
|
+
resilient_socket (0.4.0)
|
13
29
|
semantic_logger
|
14
30
|
ruby_protobuf (0.4.11)
|
15
|
-
semantic_logger (0.
|
31
|
+
semantic_logger (0.11.4)
|
16
32
|
sync_attr
|
17
|
-
|
18
|
-
|
19
|
-
shoulda-
|
20
|
-
|
21
|
-
shoulda-
|
33
|
+
thread_safe
|
34
|
+
shoulda (3.3.2)
|
35
|
+
shoulda-context (~> 1.0.1)
|
36
|
+
shoulda-matchers (~> 1.4.1)
|
37
|
+
shoulda-context (1.0.1)
|
38
|
+
shoulda-matchers (1.4.2)
|
22
39
|
activesupport (>= 3.0.0)
|
40
|
+
bourne (~> 1.1.2)
|
23
41
|
sync_attr (0.1.1)
|
42
|
+
thread_safe (0.0.3)
|
43
|
+
timers (1.0.1)
|
24
44
|
|
25
45
|
PLATFORMS
|
26
46
|
ruby
|
@@ -28,8 +48,12 @@ PLATFORMS
|
|
28
48
|
DEPENDENCIES
|
29
49
|
bson
|
30
50
|
bson_ext
|
51
|
+
celluloid-io
|
52
|
+
gene_pool
|
31
53
|
multi_json
|
54
|
+
rake
|
32
55
|
resilient_socket
|
33
56
|
ruby_protobuf
|
34
57
|
semantic_logger
|
35
58
|
shoulda
|
59
|
+
thread_safe
|
data/README.md
CHANGED
@@ -1,22 +1,56 @@
|
|
1
1
|
ruby_skynet
|
2
2
|
===========
|
3
3
|
|
4
|
-
Ruby Client for calling [Skynet](https://github.com/
|
5
|
-
|
4
|
+
Ruby Client for calling [Skynet](https://github.com/skynetservices/skynet) services, and
|
5
|
+
the server side so that [Skynet](https://github.com/skynetservices/skynet) services can be hosted in Ruby
|
6
6
|
|
7
|
-
* http://github.com/
|
7
|
+
* http://github.com/skynetservices/ruby_skynet
|
8
8
|
|
9
|
-
### Example
|
9
|
+
### Client Example
|
10
10
|
|
11
11
|
```ruby
|
12
12
|
require 'rubygems'
|
13
13
|
require 'ruby_skynet'
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
client = RubySkynet::Client.new('TutorialService')
|
16
|
+
p client.call('AddOne', :value => 5)
|
17
|
+
```
|
18
|
+
|
19
|
+
For details on installing and running the GoLang Tutorial Service: https://github.com/skynetservices/skynet/wiki/Service-Tutorial
|
20
|
+
|
21
|
+
### Server Example
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'rubygems'
|
25
|
+
require 'ruby_skynet'
|
26
|
+
|
27
|
+
RubySkynet::Server.port = 2000
|
28
|
+
RubySkynet::Server.hostname = 'localhost'
|
29
|
+
|
30
|
+
# Just echo back any parameters received when the echo method is called
|
31
|
+
class EchoService
|
32
|
+
include RubySkynet::Service
|
33
|
+
|
34
|
+
# Methods implemented by this service
|
35
|
+
# Must take a Hash as input
|
36
|
+
# Must Return a Hash response or nil for no response
|
37
|
+
def echo(params)
|
38
|
+
params
|
39
|
+
end
|
16
40
|
end
|
41
|
+
|
42
|
+
# Start the server
|
43
|
+
RubySkynet::Server.start
|
17
44
|
```
|
18
45
|
|
19
|
-
|
46
|
+
Client to call the above Service
|
47
|
+
```ruby
|
48
|
+
require 'rubygems'
|
49
|
+
require 'ruby_skynet'
|
50
|
+
|
51
|
+
client = RubySkynet::Client.new('EchoService')
|
52
|
+
p client.call('echo', :hello => 'world')
|
53
|
+
```
|
20
54
|
|
21
55
|
### Logging
|
22
56
|
|
@@ -26,12 +60,12 @@ calls can be enabled as follows:
|
|
26
60
|
```ruby
|
27
61
|
require 'rubygems'
|
28
62
|
require 'ruby_skynet'
|
63
|
+
|
29
64
|
SemanticLogger::Logger.default_level = :trace
|
30
65
|
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('skynet.log')
|
31
66
|
|
32
|
-
RubySkynet::Client.
|
33
|
-
|
34
|
-
end
|
67
|
+
client = RubySkynet::Client.new('EchoService')
|
68
|
+
p client.call('echo', :hello => 'world')
|
35
69
|
```
|
36
70
|
|
37
71
|
### Architecture
|
@@ -45,17 +79,19 @@ project for marshaling data for communicating with doozer
|
|
45
79
|
|
46
80
|
- Ruby MRI 1.8.7 (or above), Ruby 1.9.3, Or JRuby 1.6.3 (or above)
|
47
81
|
- [SemanticLogger](http://github.com/ClarityServices/semantic_logger)
|
48
|
-
- [ResilientSocket](
|
82
|
+
- [ResilientSocket](https://github.com/ClarityServices/resilient_socket)
|
49
83
|
- [ruby_protobuf](https://github.com/macks/ruby-protobuf)
|
50
84
|
- [multi_json](https://github.com/intridea/multi_json)
|
51
85
|
|
86
|
+
The server to host services in Ruby also requires:
|
87
|
+
- [Celluloid::io](https://github.com/celluloid/celluloid-io)
|
88
|
+
|
52
89
|
### Install
|
53
90
|
|
54
91
|
gem install ruby_skynet
|
55
92
|
|
56
93
|
### Future
|
57
94
|
|
58
|
-
* Implement Skynet Service in Ruby so that it can be called from Go lang, etc.
|
59
95
|
* Immediately drop connections to a service on a host when that instance
|
60
96
|
shuts down or stops. ( Doozer::Wait )
|
61
97
|
* More intelligent selection of available Skynet services. For example
|
@@ -68,7 +104,7 @@ Want to contribute to Ruby Skynet?
|
|
68
104
|
|
69
105
|
First clone the repo and run the tests:
|
70
106
|
|
71
|
-
git clone git://github.com/
|
107
|
+
git clone git://github.com/skynetservices/ruby_skynet.git
|
72
108
|
cd ruby_skynet
|
73
109
|
ruby -S rake test
|
74
110
|
|
@@ -82,15 +118,15 @@ Once you've made your great commits:
|
|
82
118
|
1. [Fork](http://help.github.com/forking/) ruby_skynet
|
83
119
|
2. Create a topic branch - `git checkout -b my_branch`
|
84
120
|
3. Push to your branch - `git push origin my_branch`
|
85
|
-
4. Create an [Issue](http://github.com/
|
121
|
+
4. Create an [Issue](http://github.com/skynetservices/ruby_skynet/issues) with a link to your branch
|
86
122
|
5. That's it!
|
87
123
|
|
88
124
|
Meta
|
89
125
|
----
|
90
126
|
|
91
|
-
* Code: `git clone git://github.com/
|
92
|
-
* Home: <https://github.com/
|
93
|
-
* Bugs: <http://github.com/
|
127
|
+
* Code: `git clone git://github.com/skynetservices/ruby_skynet.git`
|
128
|
+
* Home: <https://github.com/skynetservices/ruby_skynet>
|
129
|
+
* Bugs: <http://github.com/skynetservices/ruby_skynet/issues>
|
94
130
|
* Gems: <http://rubygems.org/gems/ruby_skynet>
|
95
131
|
|
96
132
|
This project uses [Semantic Versioning](http://semver.org/).
|
data/Rakefile
CHANGED
data/lib/ruby_skynet.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
require 'semantic_logger'
|
2
|
-
require 'resilient_socket'
|
3
|
-
|
4
2
|
require 'ruby_skynet/exceptions'
|
5
3
|
require 'ruby_skynet/version'
|
6
4
|
module RubySkynet
|
7
5
|
module Doozer
|
8
|
-
autoload :Client,
|
6
|
+
autoload :Client, 'ruby_skynet/doozer/client'
|
9
7
|
end
|
10
8
|
autoload :Registry, 'ruby_skynet/registry'
|
11
9
|
autoload :Connection, 'ruby_skynet/connection'
|
10
|
+
autoload :Common, 'ruby_skynet/common'
|
12
11
|
autoload :Client, 'ruby_skynet/client'
|
12
|
+
autoload :Service, 'ruby_skynet/service'
|
13
|
+
autoload :Server, 'ruby_skynet/server'
|
13
14
|
end
|
data/lib/ruby_skynet/client.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
module RubySkynet
|
2
|
+
module Common
|
3
|
+
|
4
|
+
# Returns a BSON document read from the socket.
|
5
|
+
# Returns nil if the operation times out or if a network
|
6
|
+
# connection failure occurs
|
7
|
+
def self.read_bson_document(socket)
|
8
|
+
bytebuf = BSON::ByteBuffer.new
|
9
|
+
# Read 4 byte size of following BSON document
|
10
|
+
bytes = socket.read(4)
|
11
|
+
|
12
|
+
# Read BSON document
|
13
|
+
sz = bytes.unpack("V")[0]
|
14
|
+
raise "Invalid Data received from server:#{bytes.inspect}" unless sz
|
15
|
+
|
16
|
+
bytebuf.append!(bytes)
|
17
|
+
bytebuf.append!(socket.read(sz - 4))
|
18
|
+
return BSON.deserialize(bytebuf)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'bson'
|
2
2
|
require 'gene_pool'
|
3
3
|
require 'thread_safe'
|
4
|
+
require 'resilient_socket'
|
4
5
|
|
5
6
|
#
|
6
7
|
# RubySkynet Connection
|
@@ -80,7 +81,7 @@ module RubySkynet
|
|
80
81
|
# ClientID string
|
81
82
|
# ClientID is a UUID that is used by the client to identify itself in RPC requests.
|
82
83
|
@logger.debug "Waiting for Service Handshake"
|
83
|
-
service_handshake =
|
84
|
+
service_handshake = Common.read_bson_document(socket)
|
84
85
|
@logger.trace 'Service Handshake', service_handshake
|
85
86
|
|
86
87
|
# #TODO When a reconnect returns registered == false need to throw an exception
|
@@ -159,12 +160,12 @@ module RubySkynet
|
|
159
160
|
# Since Send does not affect state on the server we can also retry reads
|
160
161
|
if idempotent
|
161
162
|
@logger.debug "Reading header from server"
|
162
|
-
header =
|
163
|
+
header = Common.read_bson_document(socket)
|
163
164
|
@logger.debug 'Response Header', header
|
164
165
|
|
165
166
|
# Read the BSON response document
|
166
167
|
@logger.debug "Reading response from server"
|
167
|
-
response =
|
168
|
+
response = Common.read_bson_document(socket)
|
168
169
|
@logger.trace 'Response', response
|
169
170
|
end
|
170
171
|
end
|
@@ -174,12 +175,12 @@ module RubySkynet
|
|
174
175
|
unless idempotent
|
175
176
|
# Read header first as a separate BSON document
|
176
177
|
@logger.debug "Reading header from server"
|
177
|
-
header =
|
178
|
+
header = Common.read_bson_document(socket)
|
178
179
|
@logger.debug 'Response Header', header
|
179
180
|
|
180
181
|
# Read the BSON response document
|
181
182
|
@logger.debug "Reading response from server"
|
182
|
-
response =
|
183
|
+
response = Common.read_bson_document(socket)
|
183
184
|
@logger.trace 'Response', response
|
184
185
|
end
|
185
186
|
|
@@ -220,23 +221,6 @@ module RubySkynet
|
|
220
221
|
########################
|
221
222
|
protected
|
222
223
|
|
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
224
|
# Returns a new connection pool for the specified server
|
241
225
|
def self.new_connection_pool(server, params={})
|
242
226
|
# Connection pool configuration options
|
@@ -0,0 +1,235 @@
|
|
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
|
+
|
7
|
+
#
|
8
|
+
# RubySkynet Server
|
9
|
+
#
|
10
|
+
# Hosts one or more Skynet Services
|
11
|
+
#
|
12
|
+
module RubySkynet
|
13
|
+
class Server
|
14
|
+
include Celluloid::IO
|
15
|
+
include SemanticLogger::Loggable
|
16
|
+
|
17
|
+
# TODO Make Server instance based rather than class based. Then make instance global
|
18
|
+
@@hostname = nil
|
19
|
+
@@port = 2000
|
20
|
+
@@region = 'Development'
|
21
|
+
@@server = nil
|
22
|
+
|
23
|
+
# Region under which to register Skynet services
|
24
|
+
# Default: 'Development'
|
25
|
+
def self.region
|
26
|
+
@@region
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.region=(region)
|
30
|
+
@@region = region
|
31
|
+
end
|
32
|
+
|
33
|
+
# Port to listen to requests on
|
34
|
+
# Default: 2000
|
35
|
+
def self.port
|
36
|
+
@@port
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.port=(port)
|
40
|
+
@@port = port
|
41
|
+
end
|
42
|
+
|
43
|
+
# Override the hostname at which this server is running
|
44
|
+
# Useful when the service is behind a firewall or NAT device
|
45
|
+
def self.hostname=(hostname)
|
46
|
+
@@hostname = hostname
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns [String] hostname of the current server
|
50
|
+
def self.hostname
|
51
|
+
@@hostname ||= Socket.gethostname
|
52
|
+
end
|
53
|
+
|
54
|
+
@@services = ThreadSafe::Hash.new
|
55
|
+
|
56
|
+
# Services currently loaded and available at this server when running
|
57
|
+
def self.services
|
58
|
+
@@services
|
59
|
+
end
|
60
|
+
|
61
|
+
# Registers a Service Class as being available at this port
|
62
|
+
def self.register_service(klass)
|
63
|
+
logger.debug "Registering Service: #{klass.name} with name: #{klass.service_name}"
|
64
|
+
@@services[klass.service_name] = klass
|
65
|
+
register_service_in_doozer(klass) if running?
|
66
|
+
end
|
67
|
+
|
68
|
+
# De-register service in doozer
|
69
|
+
def self.deregister_service(klass)
|
70
|
+
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
71
|
+
doozer.delete(klass.service_key) rescue nil
|
72
|
+
end
|
73
|
+
@@services.delete(klass.service_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns whether the server is running
|
77
|
+
def self.running?
|
78
|
+
(@@server != nil) && @@server.running?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Start the Server
|
82
|
+
def self.start
|
83
|
+
@@server = new
|
84
|
+
@@server.start
|
85
|
+
end
|
86
|
+
|
87
|
+
# Stop the Server
|
88
|
+
def self.stop
|
89
|
+
@@server.terminate
|
90
|
+
@@server = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def finalize
|
94
|
+
@server.close if @server
|
95
|
+
logger.info "Skynet Server Stopped"
|
96
|
+
|
97
|
+
# Deregister services hosted by this server
|
98
|
+
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
99
|
+
self.class.services.each_value do |klass|
|
100
|
+
doozer.delete(klass.service_key) rescue nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
logger.info "Skynet Services De-registered in Doozer"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns whether the server is running
|
107
|
+
def running?
|
108
|
+
(@server != nil) && !@server.closed?
|
109
|
+
end
|
110
|
+
|
111
|
+
############################################################################
|
112
|
+
protected
|
113
|
+
|
114
|
+
attr_accessor :server
|
115
|
+
|
116
|
+
# Register the supplied service in doozer
|
117
|
+
def self.register_service_in_doozer(klass)
|
118
|
+
config = {
|
119
|
+
"Config" => {
|
120
|
+
"UUID" => "#{Server.hostname}:#{Server.port}-#{$$}-#{klass.name}-#{klass.object_id}",
|
121
|
+
"Name" => klass.service_name,
|
122
|
+
"Version" => klass.service_version.to_s,
|
123
|
+
"Region" => Server.region,
|
124
|
+
"ServiceAddr" => {
|
125
|
+
"IPAddress" => Server.hostname,
|
126
|
+
"Port" => Server.port,
|
127
|
+
"MaxPort" => Server.port + 999
|
128
|
+
},
|
129
|
+
},
|
130
|
+
"Registered" => true
|
131
|
+
}
|
132
|
+
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
133
|
+
doozer[klass.service_key] = MultiJson.encode(config)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Start the server so that it can start taking RPC calls
|
138
|
+
# Returns false if the server is already running
|
139
|
+
def start
|
140
|
+
return false if running?
|
141
|
+
|
142
|
+
# Since we included Celluloid::IO, we're actually making a
|
143
|
+
# Celluloid::IO::TCPServer here
|
144
|
+
# TODO If port is in use, try the next port in sequence
|
145
|
+
# TODO make port to listen on configurable
|
146
|
+
@server = TCPServer.new('0.0.0.0', self.class.port)
|
147
|
+
run!
|
148
|
+
|
149
|
+
# Register services hosted by this server
|
150
|
+
self.class.services.each_pair {|key, klass| self.class.register_service_in_doozer(klass)}
|
151
|
+
true
|
152
|
+
end
|
153
|
+
|
154
|
+
def run
|
155
|
+
logger.info("Starting listener on #{self.class.hostname}:#{self.class.port}")
|
156
|
+
loop do
|
157
|
+
logger.debug "Waiting for a client to connect"
|
158
|
+
begin
|
159
|
+
handle_connection!(@server.accept)
|
160
|
+
rescue Exception => exc
|
161
|
+
logger.error "Exception while processing connection request", exc
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Called for each message received from the client
|
167
|
+
# Returns a Hash that is sent back to the caller
|
168
|
+
def on_message(service_name, method, params)
|
169
|
+
logger.benchmark_debug "Called: #{service_name}##{method}" do
|
170
|
+
logger.trace "Method Call: #{method} with parameters:", params
|
171
|
+
klass = Server.services[service_name]
|
172
|
+
raise "Invalid Skynet RPC call, service: #{service_name} is not available at this server" unless klass
|
173
|
+
service = klass.new
|
174
|
+
raise "Invalid Skynet RPC call, method: #{method} does not exist for service: #{service_name}" unless service.respond_to?(method)
|
175
|
+
# TODO Use pool of services, or Celluloid here
|
176
|
+
service.send(method, params)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Called for each client connection
|
181
|
+
def handle_connection(client)
|
182
|
+
logger.debug "Client connected, waiting for data from client"
|
183
|
+
|
184
|
+
# Process handshake
|
185
|
+
handshake = {
|
186
|
+
'registered' => true,
|
187
|
+
'clientid' => BSON::ObjectId.new.to_s
|
188
|
+
}
|
189
|
+
client.write(BSON.serialize(handshake))
|
190
|
+
Common.read_bson_document(client)
|
191
|
+
|
192
|
+
while(header = Common.read_bson_document(client)) do
|
193
|
+
logger.debug "\n******************"
|
194
|
+
logger.debug "Received Request"
|
195
|
+
logger.trace 'Header', header
|
196
|
+
|
197
|
+
service_name = header['servicemethod'] # "#{service_name}.Forward",
|
198
|
+
raise "Invalid Skynet RPC Request, missing servicemethod" unless service_name
|
199
|
+
match = service_name.match /(.*)\.Forward$/
|
200
|
+
raise "Invalid Skynet RPC Request, servicemethod must end with '.Forward'" unless match
|
201
|
+
service_name = match[1]
|
202
|
+
|
203
|
+
request = Common.read_bson_document(client)
|
204
|
+
logger.trace 'Request', request
|
205
|
+
break unless request
|
206
|
+
params = BSON.deserialize(request['in'])
|
207
|
+
logger.trace 'Parameters', params
|
208
|
+
reply = begin
|
209
|
+
on_message(service_name, request['method'].to_sym, params)
|
210
|
+
rescue Exception => exc
|
211
|
+
logger.error "Exception while calling service: #{service_name}", exc
|
212
|
+
# TODO Return exception in header
|
213
|
+
{ :exception => {:message => exc.message, :class => exc.class.name} }
|
214
|
+
end
|
215
|
+
|
216
|
+
if reply
|
217
|
+
logger.debug "Sending Header"
|
218
|
+
# For this test we just send back the received header
|
219
|
+
client.write(BSON.serialize(header))
|
220
|
+
|
221
|
+
logger.debug "Sending Reply"
|
222
|
+
logger.trace 'Reply', reply
|
223
|
+
client.write(BSON.serialize({'out' => BSON.serialize(reply).to_s}))
|
224
|
+
else
|
225
|
+
logger.debug "Closing client since no reply is being sent back"
|
226
|
+
break
|
227
|
+
end
|
228
|
+
end
|
229
|
+
# Disconnect from the client
|
230
|
+
client.close
|
231
|
+
logger.debug "Disconnected from the client"
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Doozer entries are in json
|
2
|
+
require 'multi_json'
|
3
|
+
require 'thread_safe'
|
4
|
+
|
5
|
+
#
|
6
|
+
# RubySkynet Service
|
7
|
+
#
|
8
|
+
# Supports
|
9
|
+
# Hosting Skynet Services
|
10
|
+
# Skynet Service registration
|
11
|
+
#
|
12
|
+
module RubySkynet
|
13
|
+
module Service
|
14
|
+
def self.included(base)
|
15
|
+
base.extend ClassMethods
|
16
|
+
base.class_eval do
|
17
|
+
include SemanticLogger::Loggable
|
18
|
+
|
19
|
+
sync_cattr_reader :logger do
|
20
|
+
SemanticLogger::Logger.new(self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# Register the service with the Server
|
24
|
+
# The server will publish the server to Doozer when te server is running
|
25
|
+
Server.register_service(base)
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
# Name of this service to Register with Skynet
|
30
|
+
# Default: class name
|
31
|
+
def service_name
|
32
|
+
@@service_name ||= name.gsub('::', '.')
|
33
|
+
end
|
34
|
+
|
35
|
+
def service_name=(service_name)
|
36
|
+
@@service_name = service_name
|
37
|
+
end
|
38
|
+
|
39
|
+
# Version of this service to register with Skynet, defaults to 1
|
40
|
+
# Default: 1
|
41
|
+
def service_version
|
42
|
+
@@service_version ||= 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def service_version=(service_version)
|
46
|
+
@@service_version = service_version
|
47
|
+
end
|
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
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
data/lib/ruby_skynet/version.rb
CHANGED
data/test.log
CHANGED
Binary file
|
data/test/doozer_client_test.rb
CHANGED
@@ -6,9 +6,6 @@ require 'test/unit'
|
|
6
6
|
require 'shoulda'
|
7
7
|
require 'ruby_skynet/doozer/client'
|
8
8
|
|
9
|
-
SemanticLogger::Logger.default_level = :trace
|
10
|
-
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
|
11
|
-
|
12
9
|
# NOTE:
|
13
10
|
# This test assumes that doozerd is running locally on the default port of 8046
|
14
11
|
|
@@ -9,8 +9,11 @@ require 'ruby_skynet'
|
|
9
9
|
require 'simple_server'
|
10
10
|
require 'multi_json'
|
11
11
|
|
12
|
-
|
13
|
-
SemanticLogger::Logger.appenders
|
12
|
+
# Register an appender if one is not already registered
|
13
|
+
if SemanticLogger::Logger.appenders.size == 0
|
14
|
+
SemanticLogger::Logger.default_level = :trace
|
15
|
+
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
|
16
|
+
end
|
14
17
|
|
15
18
|
# Unit Test for ResilientSocket::TCPClient
|
16
19
|
class RubySkynetClientTest < Test::Unit::TestCase
|
@@ -59,7 +62,7 @@ class RubySkynetClientTest < Test::Unit::TestCase
|
|
59
62
|
end
|
60
63
|
|
61
64
|
teardown do
|
62
|
-
@server.
|
65
|
+
@server.terminate if @server
|
63
66
|
# De-register server in doozer
|
64
67
|
RubySkynet::Registry.doozer_pool.with_connection do |doozer|
|
65
68
|
doozer.delete("/services/#{@service_name}/#{@version}/#{@region}/#{@ip_address}/#{@port}") rescue nil
|
@@ -0,0 +1,73 @@
|
|
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 'ruby_skynet'
|
8
|
+
|
9
|
+
# Register an appender if one is not already registered
|
10
|
+
if SemanticLogger::Logger.appenders.size == 0
|
11
|
+
SemanticLogger::Logger.default_level = :trace
|
12
|
+
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
|
13
|
+
end
|
14
|
+
|
15
|
+
RubySkynet::Server.port = 2100
|
16
|
+
RubySkynet::Server.region = 'Test'
|
17
|
+
RubySkynet::Server.hostname = 'localhost'
|
18
|
+
|
19
|
+
class TestService
|
20
|
+
include RubySkynet::Service
|
21
|
+
|
22
|
+
# Methods implemented by this service
|
23
|
+
# Must take a Hash as input
|
24
|
+
# Must Return a Hash response or nil for no response
|
25
|
+
def echo(params)
|
26
|
+
params
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Unit Test for ResilientSocket::TCPClient
|
31
|
+
class RubySkynetServiceTest < Test::Unit::TestCase
|
32
|
+
context 'RubySkynet::Service' do
|
33
|
+
context "with server" do
|
34
|
+
setup do
|
35
|
+
RubySkynet::Server.start
|
36
|
+
@service_name = 'TestService'
|
37
|
+
@version = 1
|
38
|
+
@region = 'Test'
|
39
|
+
@doozer_key = "/services/#{@service_name}/#{@version}/#{@region}/localhost/2100"
|
40
|
+
end
|
41
|
+
|
42
|
+
teardown do
|
43
|
+
begin
|
44
|
+
RubySkynet::Server.stop
|
45
|
+
rescue Celluloid::DeadActorError
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
should "have correct service key" do
|
50
|
+
assert_equal @doozer_key, TestService.service_key
|
51
|
+
end
|
52
|
+
|
53
|
+
should "register service" do
|
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
|
60
|
+
setup do
|
61
|
+
@client = RubySkynet::Client.new(@service_name, @version, @region)
|
62
|
+
end
|
63
|
+
|
64
|
+
should "successfully send and receive data" do
|
65
|
+
reply = @client.call(:echo, 'some' => 'parameters')
|
66
|
+
assert_equal 'some', reply.keys.first
|
67
|
+
assert_equal 'parameters', reply.values.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/test/simple_server.rb
CHANGED
@@ -2,11 +2,11 @@ require 'rubygems'
|
|
2
2
|
require 'socket'
|
3
3
|
require 'bson'
|
4
4
|
require 'semantic_logger'
|
5
|
+
require 'celluloid/io'
|
5
6
|
|
6
7
|
# This a simple stand-alone server that does not use the Skynet code so that
|
7
8
|
# the Skynet code can be tested
|
8
9
|
|
9
|
-
|
10
10
|
# Read the bson document, returning nil if the IO is closed
|
11
11
|
# before receiving any data or a complete BSON document
|
12
12
|
def read_bson_document(io)
|
@@ -26,35 +26,25 @@ end
|
|
26
26
|
# Simple single threaded server for testing purposes using a local socket
|
27
27
|
# Sends and receives BSON Messages
|
28
28
|
class SimpleServer
|
29
|
-
|
30
|
-
def initialize(port = 2000)
|
31
|
-
start(port)
|
32
|
-
end
|
29
|
+
include Celluloid::IO
|
33
30
|
|
34
|
-
def
|
35
|
-
|
31
|
+
def initialize(port)
|
32
|
+
# Since we included Celluloid::IO, we're actually making a
|
33
|
+
# Celluloid::IO::TCPServer here
|
34
|
+
@server = TCPServer.new('127.0.0.1', port)
|
36
35
|
@logger = SemanticLogger::Logger.new(self.class)
|
36
|
+
run!
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
# Wait for a client to connect
|
43
|
-
on_request(@server.accept)
|
44
|
-
end
|
39
|
+
def run
|
40
|
+
loop do
|
41
|
+
@logger.debug "Waiting for a client to connect"
|
42
|
+
handle_connection!(@server.accept)
|
45
43
|
end
|
46
44
|
end
|
47
45
|
|
48
|
-
def
|
49
|
-
if @
|
50
|
-
@thread.kill
|
51
|
-
@thread.join
|
52
|
-
@thread = nil
|
53
|
-
end
|
54
|
-
begin
|
55
|
-
@server.close if @server
|
56
|
-
rescue IOError
|
57
|
-
end
|
46
|
+
def finalize
|
47
|
+
@server.close if @server
|
58
48
|
end
|
59
49
|
|
60
50
|
# Called for each message received from the client
|
@@ -78,8 +68,7 @@ class SimpleServer
|
|
78
68
|
end
|
79
69
|
|
80
70
|
# Called for each client connection
|
81
|
-
|
82
|
-
def on_request(client)
|
71
|
+
def handle_connection(client)
|
83
72
|
@logger.debug "Client connected, waiting for data from client"
|
84
73
|
|
85
74
|
# Process handshake
|
@@ -87,7 +76,7 @@ class SimpleServer
|
|
87
76
|
'registered' => true,
|
88
77
|
'clientid' => '123'
|
89
78
|
}
|
90
|
-
client.
|
79
|
+
client.write(BSON.serialize(handshake))
|
91
80
|
read_bson_document(client)
|
92
81
|
|
93
82
|
while(header = read_bson_document(client)) do
|
@@ -102,19 +91,17 @@ class SimpleServer
|
|
102
91
|
if reply = on_message(request['method'], BSON.deserialize(request['in']))
|
103
92
|
@logger.debug "Sending Header"
|
104
93
|
# For this test we just send back the received header
|
105
|
-
client.
|
94
|
+
client.write(BSON.serialize(header))
|
106
95
|
|
107
96
|
@logger.debug "Sending Reply"
|
108
97
|
@logger.trace 'Reply', reply
|
109
|
-
client.
|
98
|
+
client.write(BSON.serialize({'out' => BSON.serialize(reply).to_s}))
|
110
99
|
else
|
111
100
|
@logger.debug "Closing client since no reply is being sent back"
|
112
101
|
@server.close
|
113
102
|
client.close
|
114
103
|
@logger.debug "Server closed"
|
115
|
-
|
116
|
-
@logger.debug "thread killed"
|
117
|
-
start(2000)
|
104
|
+
run!
|
118
105
|
@logger.debug "Server Restarted"
|
119
106
|
break
|
120
107
|
end
|
@@ -123,12 +110,12 @@ class SimpleServer
|
|
123
110
|
client.close
|
124
111
|
@logger.debug "Disconnected from the client"
|
125
112
|
end
|
126
|
-
|
127
113
|
end
|
128
114
|
|
129
115
|
if $0 == __FILE__
|
130
116
|
SemanticLogger::Logger.default_level = :trace
|
131
117
|
SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(STDOUT)
|
118
|
+
Celluloid.logger = SemanticLogger::Logger.new('Celluloid')
|
132
119
|
server = SimpleServer.new(2000)
|
133
120
|
server.thread.join
|
134
121
|
end
|
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.3.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-
|
12
|
+
date: 2012-12-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: semantic_logger
|
@@ -98,31 +98,34 @@ executables: []
|
|
98
98
|
extensions: []
|
99
99
|
extra_rdoc_files: []
|
100
100
|
files:
|
101
|
-
- dev.log
|
102
101
|
- Gemfile
|
103
102
|
- Gemfile.lock
|
103
|
+
- LICENSE.txt
|
104
|
+
- README.md
|
105
|
+
- Rakefile
|
106
|
+
- lib/ruby_skynet.rb
|
104
107
|
- lib/ruby_skynet/client.rb
|
108
|
+
- lib/ruby_skynet/common.rb
|
105
109
|
- lib/ruby_skynet/connection.rb
|
106
110
|
- lib/ruby_skynet/doozer/client.rb
|
107
111
|
- lib/ruby_skynet/doozer/exceptions.rb
|
108
112
|
- lib/ruby_skynet/doozer/msg.pb.rb
|
109
113
|
- lib/ruby_skynet/exceptions.rb
|
110
114
|
- lib/ruby_skynet/registry.rb
|
115
|
+
- lib/ruby_skynet/server.rb
|
116
|
+
- lib/ruby_skynet/service.rb
|
111
117
|
- lib/ruby_skynet/version.rb
|
112
|
-
- lib/ruby_skynet.rb
|
113
|
-
- LICENSE.txt
|
114
118
|
- nbproject/private/config.properties
|
115
119
|
- nbproject/private/private.properties
|
116
120
|
- nbproject/private/private.xml
|
117
121
|
- nbproject/private/rake-d.txt
|
118
122
|
- nbproject/project.properties
|
119
123
|
- nbproject/project.xml
|
120
|
-
-
|
121
|
-
- README.md
|
124
|
+
- test.log
|
122
125
|
- test/doozer_client_test.rb
|
123
126
|
- test/ruby_skynet_client_test.rb
|
127
|
+
- test/ruby_skynet_service_test.rb
|
124
128
|
- test/simple_server.rb
|
125
|
-
- test.log
|
126
129
|
homepage: https://github.com/ClarityServices/ruby_skynet
|
127
130
|
licenses: []
|
128
131
|
post_install_message:
|
@@ -137,7 +140,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
140
|
version: '0'
|
138
141
|
segments:
|
139
142
|
- 0
|
140
|
-
hash:
|
143
|
+
hash: 434624136862152057
|
141
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
145
|
none: false
|
143
146
|
requirements:
|
data/dev.log
DELETED
Binary file
|