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 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.8)
4
+ activesupport (3.2.9)
5
5
  i18n (~> 0.6)
6
6
  multi_json (~> 1.0)
7
- bson (1.7.0)
8
- bson_ext (1.7.0)
9
- bson (~> 1.7.0)
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
- multi_json (1.3.6)
12
- resilient_socket (0.2.1)
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.7.0)
31
+ semantic_logger (0.11.4)
16
32
  sync_attr
17
- shoulda (3.3.0)
18
- shoulda-context (~> 1.0)
19
- shoulda-matchers (~> 1.4)
20
- shoulda-context (1.0.0)
21
- shoulda-matchers (1.4.0)
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/bketelsen/skynet) services.
5
- Will also implement the server side so that [Skynet](https://github.com/bketelsen/skynet) Services can be hosted in Ruby
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/ClarityServices/ruby_skynet
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
- RubySkynet::Client.connect('TutorialService') do |tutorial_service|
15
- p tutorial_service.call('AddOne', 'value' => 5)
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
- For details on installing and running the Tutorial Service: https://github.com/bketelsen/skynet/wiki/Service-Tutorial
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.connect('TutorialService') do |tutorial_service|
33
- p tutorial_service.call('AddOne', 'value' => 5)
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](http://github.com/ClarityServices/ruby_skynet)
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/ClarityServices/ruby_skynet.git
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/ClarityServices/ruby_skynet/issues) with a link to your branch
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/ClarityServices/ruby_skynet.git`
92
- * Home: <https://github.com/ClarityServices/ruby_skynet>
93
- * Bugs: <http://github.com/ClarityServices/ruby_skynet/issues>
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
@@ -5,6 +5,7 @@ require 'rubygems'
5
5
  require 'rake/clean'
6
6
  require 'rake/testtask'
7
7
  require 'date'
8
+ require 'semantic_logger'
8
9
  require 'ruby_skynet/version'
9
10
 
10
11
  desc "Build gem"
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, 'ruby_skynet/doozer/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
@@ -8,8 +8,6 @@ require 'bson'
8
8
  #
9
9
  module RubySkynet
10
10
  class Client
11
- include SyncAttr
12
-
13
11
  # Returns a new RubySkynet Client for the named service
14
12
  #
15
13
  # Calls to an instance of the Client are thread-safe and can be called
@@ -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 = self.class.read_bson_document(socket)
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 = self.class.read_bson_document(socket)
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 = self.class.read_bson_document(socket)
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 = self.class.read_bson_document(socket)
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 = self.class.read_bson_document(socket)
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
+
@@ -1,3 +1,3 @@
1
1
  module RubySkynet #:nodoc
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/test.log CHANGED
Binary file
@@ -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
- SemanticLogger::Logger.default_level = :trace
13
- SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
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.stop if @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
@@ -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
- attr_reader :thread
30
- def initialize(port = 2000)
31
- start(port)
32
- end
29
+ include Celluloid::IO
33
30
 
34
- def start(port)
35
- @server = TCPServer.open(port)
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
- @thread = Thread.new do
39
- loop do
40
- @logger.debug "Waiting for a client to connect"
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 stop
49
- if @thread
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
- # In a real server each request would be handled in a separate thread
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.print(BSON.serialize(handshake))
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.print(BSON.serialize(header))
94
+ client.write(BSON.serialize(header))
106
95
 
107
96
  @logger.debug "Sending Reply"
108
97
  @logger.trace 'Reply', reply
109
- client.print(BSON.serialize({'out' => BSON.serialize(reply).to_s}))
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
- #@thread.kill
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.2.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-10-18 00:00:00.000000000 Z
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
- - Rakefile
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: -4330021218658134009
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