ruby_skynet 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +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
|