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 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