ruby_skynet 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 16525cda69fa5554ce2851afcea16d4d379e1c3e
4
- data.tar.gz: b02c07c9d88035fc5018e18548dc0e868f640716
3
+ metadata.gz: f15ead62305cae332e890c1ad63a3f11e9085ffe
4
+ data.tar.gz: aa3243ae8229d4cd7a5f82ac673fd3e7cc700b17
5
5
  SHA512:
6
- metadata.gz: 461650dbc05cd7febf853c2f7416e22f089cec61fa4e5aea188cef90573452e6ea1f8cc43443132b94071420e9727cacd605662a2d35423baf3a2acecbc2132e
7
- data.tar.gz: 9bfa3b9751bd03753a849e34c1c4a3eb9c9f9de04af1efe34131aa7fdcd3c59aa1f8e3fb885ecbe61e79abd2aea810804a9c169c9a26ed0d553ecf0e5210be23
6
+ metadata.gz: 363ed46b3ee9182fa572fb00724031f01d077600a304ac3d38ccbbb023b3c33ff25872c72b9eeb7c130e4784ce9c6c2ff807a9c522d391ef31a8de9564fdd7e3
7
+ data.tar.gz: 4eeb6d97725af7fb087e40afbc8b8d459c8889b84c1d4b1756885eedc3322adbf22a5afd89d44c97ad456e7441e0dd09fb90007c1a28204835638ab1e9404faf
data/README.md CHANGED
@@ -24,9 +24,6 @@ For details on installing and running the GoLang Tutorial Service: https://githu
24
24
  require 'rubygems'
25
25
  require 'ruby_skynet'
26
26
 
27
- RubySkynet::Server.port = 2000
28
- RubySkynet::Server.hostname = '127.0.0.1'
29
-
30
27
  # Just echo back any parameters received when the echo method is called
31
28
  class EchoService
32
29
  include RubySkynet::Service
@@ -48,8 +45,12 @@ Client to call the above Service
48
45
  require 'rubygems'
49
46
  require 'ruby_skynet'
50
47
 
51
- client = RubySkynet::Client.new('EchoService')
52
- p client.call('echo', :hello => 'world')
48
+ class EchoService
49
+ include RubySkynet::Base
50
+ end
51
+
52
+ client = EchoService.new
53
+ p client.echo(:hello => 'world')
53
54
  ```
54
55
 
55
56
  ### Logging
@@ -64,8 +65,12 @@ require 'ruby_skynet'
64
65
  SemanticLogger::Logger.default_level = :trace
65
66
  SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('skynet.log')
66
67
 
67
- client = RubySkynet::Client.new('EchoService')
68
- p client.call('echo', :hello => 'world')
68
+ class EchoService
69
+ include RubySkynet::Base
70
+ end
71
+
72
+ client = EchoService.new
73
+ p client.echo(:hello => 'world')
69
74
  ```
70
75
 
71
76
  ### Architecture
@@ -83,20 +88,10 @@ project for marshaling data for communicating with doozer
83
88
  - [ruby_protobuf](https://github.com/macks/ruby-protobuf)
84
89
  - [multi_json](https://github.com/intridea/multi_json)
85
90
 
86
- The server to host services in Ruby also requires:
87
- - [Celluloid::io](https://github.com/celluloid/celluloid-io)
88
-
89
91
  ### Install
90
92
 
91
93
  gem install ruby_skynet
92
94
 
93
- ### Future
94
-
95
- * Immediately drop connections to a service on a host when that instance
96
- shuts down or stops. ( Doozer::Wait )
97
- * More intelligent selection of available Skynet services. For example
98
- nearest, or looking at load etc.
99
-
100
95
  Development
101
96
  -----------
102
97
 
@@ -139,7 +134,7 @@ Reid Morrison :: reidmo@gmail.com :: @reidmorrison
139
134
  License
140
135
  -------
141
136
 
142
- Copyright 2012 Clarity Services, Inc.
137
+ Copyright 2012,2013 Clarity Services, Inc.
143
138
 
144
139
  Licensed under the Apache License, Version 2.0 (the "License");
145
140
  you may not use this file except in compliance with the License.
data/Rakefile CHANGED
@@ -29,6 +29,7 @@ task :gem do |t|
29
29
  spec.add_dependency 'multi_json', '>= 1.6.1'
30
30
  spec.add_dependency 'bson'
31
31
  spec.add_dependency 'ruby_protobuf'
32
+ spec.add_dependency 'gene_pool'
32
33
  end
33
34
  Gem::Package.build gemspec
34
35
  end
@@ -0,0 +1,20 @@
1
+ module RubySkynet
2
+ module Generators
3
+ class ConfigGenerator < Rails::Generators::Base
4
+ desc "Creates a Ruby Skynet configuration file at config/ruby_skynet.yml"
5
+
6
+ def self.source_root
7
+ @_ruby_skynet_source_root ||= File.expand_path("../templates", __FILE__)
8
+ end
9
+
10
+ def app_name
11
+ Rails::Application.subclasses.first.parent.to_s.underscore
12
+ end
13
+
14
+ def create_config_file
15
+ template 'ruby_skynet.yml', File.join('config', "ruby_skynet.yml")
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,86 @@
1
+ # Ruby Skynet Client & Server Configuration Parameters
2
+ # :region [String]
3
+ # Region name to use for service lookup
4
+ # Default: Rails.env
5
+ #
6
+ # :services_path [String]
7
+ # Path within to look for service implementations that will be loaded
8
+ # when RubySkynet::Server.load_services is called
9
+ #
10
+ # :server_port [Integer]
11
+ # Optional: Only required when running a server locally
12
+ # Starting port for server to listen on
13
+ # If this port is in use the next available port will be used
14
+ # upto 999 above the server_port value
15
+ #
16
+ # :local_ip_address [String]
17
+ # Optional: The ip address at which this server instance can be reached
18
+ # by remote Skynet clients
19
+ # Note: Must be an IP address, not the hostname
20
+ #
21
+ # Doozer Configuration Parameters
22
+ # :doozer
23
+ # :servers [Array of String]
24
+ # Array of URL's of doozer servers to connect to with port numbers
25
+ # ['server1:2000', 'server2:2000']
26
+ #
27
+ # The second server will only be attempted once the first server
28
+ # cannot be connected to or has timed out on connect
29
+ # A read failure or timeout will not result in switching to the second
30
+ # server, only a connection failure or during an automatic reconnect
31
+ #
32
+ # :read_timeout [Float]
33
+ # Time in seconds to timeout on read
34
+ # Can be overridden by supplying a timeout in the read call
35
+ #
36
+ # :connect_timeout [Float]
37
+ # Time in seconds to timeout when trying to connect to the server
38
+ #
39
+ # :connect_retry_count [Fixnum]
40
+ # Number of times to retry connecting when a connection fails
41
+ #
42
+ # :connect_retry_interval [Float]
43
+ # Number of seconds between connection retry attempts after the first failed attempt
44
+ #
45
+ # :server_selector [Symbol]
46
+ # When multiple doozer servers are supplied using :servers, this option will
47
+ # determine which server is selected from the list
48
+ # :ordered
49
+ # Select a server in the order supplied in the array, with the first
50
+ # having the highest priority. The second server will only be connected
51
+ # to if the first server is unreachable
52
+ # :random
53
+ # Randomly select a server from the list every time a connection
54
+ # is established, including during automatic connection recovery.
55
+ # Default: :ordered
56
+ defaults: &defaults
57
+ :services_path: app/services
58
+ :server_port: 2000
59
+ :doozer:
60
+ :servers:
61
+ - 127.0.0.1:8046
62
+ :read_timeout: 5
63
+ :connect_timeout: 3
64
+ :connect_retry_count: 10
65
+ :connect_retry_interval: 0.5
66
+ :server_selector: :random
67
+
68
+ development:
69
+ <<: *defaults
70
+ :region: Development
71
+
72
+ test:
73
+ <<: *defaults
74
+ :region: Test
75
+
76
+ release:
77
+ <<: *defaults
78
+ :region: Release
79
+
80
+ hotfix:
81
+ <<: *defaults
82
+ :region: Hotfix
83
+
84
+ production:
85
+ <<: *defaults
86
+ :region: Production
@@ -0,0 +1,65 @@
1
+ require 'semantic_logger'
2
+
3
+ # Base class for RubySkynet Clients and Services
4
+ module RubySkynet
5
+ module Base
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.class_eval do
9
+ include SemanticLogger::Loggable
10
+ include InstanceMethods
11
+ end
12
+ end
13
+
14
+ module InstanceMethods
15
+ # Implement methods that call the remote Service
16
+ def method_missing(method, *args, &block)
17
+ result = ruby_skynet_client.call(method, *args)
18
+ # Define the method if the call was successful and no-one else already
19
+ # created the method
20
+ if result[:exception].nil? && !self.class.method_defined?(method)
21
+ self.class.send(:define_method, method) {|*args| ruby_skynet_client.call(method, *args)}
22
+ end
23
+ result
24
+ end
25
+
26
+ def ruby_skynet_client
27
+ @ruby_skynet_client ||= RubySkynet::Client.new(self.class.skynet_name, self.class.skynet_version || '*', self.class.skynet_region)
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+ # Name of this service to Register with Skynet
33
+ # Default: class name
34
+ def skynet_name
35
+ @skynet_name ||= name.gsub('::', '.')
36
+ end
37
+
38
+ def skynet_name=(skynet_name)
39
+ @skynet_name = skynet_name
40
+ end
41
+
42
+ # Version of this service to register with Skynet
43
+ # Default: nil
44
+ def skynet_version
45
+ @skynet_version ||= nil
46
+ end
47
+
48
+ def skynet_version=(skynet_version)
49
+ @skynet_version = skynet_version
50
+ end
51
+
52
+ # Region within which this service is defined
53
+ # Default: RubySkynet.region
54
+ def skynet_region
55
+ @skynet_region || ::RubySkynet.region
56
+ end
57
+
58
+ def skynet_region=(skynet_region)
59
+ @skynet_region = skynet_region
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -14,7 +14,7 @@ module RubySkynet
14
14
  # concurrently from multiple threads at the same time
15
15
  #
16
16
  # Parameters:
17
- # :service_name
17
+ # :skynet_name
18
18
  # Name of the service to look for and connect to on Skynet
19
19
  #
20
20
  # :version
@@ -33,9 +33,9 @@ module RubySkynet
33
33
  #
34
34
  # tutorial_service = RubySkynet::Client.new('TutorialService')
35
35
  # p tutorial_service.call('Add', :value => 5)
36
- def initialize(service_name, version='*', region='Development')
37
- @service_name = service_name
38
- @logger = SemanticLogger::Logger.new("#{self.class.name}: #{service_name}/#{version}/#{region}")
36
+ def initialize(skynet_name, version='*', region='Development')
37
+ @skynet_name = skynet_name
38
+ @logger = SemanticLogger::Logger.new("#{self.class.name}: #{skynet_name}/#{version}/#{region}")
39
39
  @version = version
40
40
  @region = region
41
41
  end
@@ -57,12 +57,12 @@ module RubySkynet
57
57
  # https://github.com/skynetservices/skynet/blob/master/protocol.md
58
58
  request_id = BSON::ObjectId.new.to_s
59
59
  @logger.tagged request_id do
60
- @logger.benchmark_info "Called Skynet Service: #{@service_name}.#{method_name}" do
60
+ @logger.benchmark_info "Called Skynet Service: #{@skynet_name}.#{method_name}" do
61
61
  retries = 0
62
62
  # If it cannot connect to a server, try a different server
63
63
  begin
64
- Connection.with_connection(Registry.server_for(@service_name, @version, @region), connection_params) do |connection|
65
- connection.rpc_call(request_id, @service_name, method_name, parameters)
64
+ Connection.with_connection(Registry.server_for(@skynet_name, @version, @region), connection_params) do |connection|
65
+ connection.rpc_call(request_id, @skynet_name, method_name, parameters)
66
66
  end
67
67
  rescue ResilientSocket::ConnectionFailure => exc
68
68
  if (retries < 3) && exc.cause.is_a?(Errno::ECONNREFUSED)
@@ -108,7 +108,7 @@ module RubySkynet
108
108
  # Performs a synchronous call to a Skynet server
109
109
  #
110
110
  # Parameters:
111
- # service_name [String|Symbol]:
111
+ # skynet_name [String|Symbol]:
112
112
  # Name of the method to pass in the request
113
113
  # method_name [String|Symbol]:
114
114
  # Name of the method to pass in the request
@@ -122,14 +122,14 @@ module RubySkynet
122
122
  #
123
123
  # Raises RubySkynet::ProtocolError
124
124
  # Raises RubySkynet::SkynetException
125
- def rpc_call(request_id, service_name, method_name, parameters, idempotent=false)
125
+ def rpc_call(request_id, skynet_name, method_name, parameters, idempotent=false)
126
126
  retry_count = 0
127
127
  header = nil
128
128
  response = nil
129
129
 
130
130
  socket.retry_on_connection_failure do |socket|
131
131
  header = {
132
- 'servicemethod' => "#{service_name}.Forward",
132
+ 'servicemethod' => "#{skynet_name}.Forward",
133
133
  'seq' => socket.user_data[:seq]
134
134
  }
135
135
 
@@ -8,10 +8,10 @@ module RubySkynet
8
8
  module Doozer
9
9
  class Client
10
10
 
11
+ include SemanticLogger::Loggable
12
+
11
13
  # Create a resilient client connection to a Doozer server
12
14
  def initialize(params={})
13
- @logger = SemanticLogger::Logger.new(self.class)
14
-
15
15
  # User configurable options
16
16
  params[:read_timeout] ||= 5
17
17
  params[:connect_timeout] ||= 3
@@ -25,7 +25,7 @@ module RubySkynet
25
25
  # Disable buffering the send since it is a RPC call
26
26
  params[:buffered] = false
27
27
 
28
- @logger.trace "Socket Connection parameters", params.dup
28
+ logger.trace "Socket Connection parameters", params.dup
29
29
 
30
30
  # For each new connection
31
31
  params[:on_connect] = Proc.new do |socket|
@@ -3,6 +3,7 @@ module RubySkynet
3
3
  class ProtocolError < Exception; end
4
4
  class ServiceException < Exception; end
5
5
  class InvalidServiceException < ServiceException; end
6
+ class InvalidConfigurationException < ServiceException; end
6
7
  class SkynetException < Exception; end
7
8
  class ServiceUnavailable < SkynetException; end
8
9
  end
@@ -0,0 +1,30 @@
1
+ module RubySkynet #:nodoc:
2
+ class Railtie < Rails::Railtie #:nodoc:
3
+
4
+ # Exposes RubySkynet configuration to the Rails application configuration.
5
+ #
6
+ # @example Set up configuration in the Rails app.
7
+ # module MyApplication
8
+ # class Application < Rails::Application
9
+ # config.ruby_skynet.region = "Development"
10
+ # end
11
+ # end
12
+ config.ruby_skynet = ::RubySkynet
13
+
14
+ rake_tasks do
15
+ load "ruby_skynet/railties/ruby_skynet.rake"
16
+ end
17
+
18
+ # Load RubySkynet Configuration
19
+ config.before_configuration do
20
+ config_file = Rails.root.join("config", "ruby_skynet.yml")
21
+ if config_file.file?
22
+ ::RubySkynet.configure!(config_file, Rails.env)
23
+ else
24
+ puts "\nRuby Skynet config not found."
25
+ puts "To generate one for the first time: rails generate ruby_skynet:config\n\n"
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ namespace :ruby_skynet do
2
+
3
+ desc "Start the Ruby Skynet Server.\n Rails Example: rake ruby_skynet:server\n Without Rails: SKYNET_ENV=production SKYNET_CONFIG=config/ruby_skynet rake ruby_skynet:server"
4
+ task :server => :environment do
5
+ # Configuration is automatically loaded when running under Rails
6
+ # so skip it here under Rails
7
+ unless defined?(Rails)
8
+ # Environment to use in config file
9
+ environment = ENV['SKYNET_ENV']
10
+
11
+ # Environment to use in config file
12
+ cfg_file = ENV['SKYNET_CONFIG']
13
+
14
+ # Load the configuration file
15
+ RubySkynet.configure!(cfg_file, environment)
16
+ end
17
+
18
+ # Connect to doozer
19
+ RubySkynet::Registry.service_registry
20
+
21
+ RubySkynet::Server.load_services
22
+
23
+ # Start the server
24
+ RubySkynet::Server.start
25
+ RubySkynet::Server.wait_until_server_stops
26
+ end
27
+
28
+ end
@@ -16,10 +16,12 @@ module RubySkynet
16
16
  include SyncAttr
17
17
 
18
18
  # Service Registry has the following format
19
- # Key: [String] 'service_name/version/region'
19
+ # Key: [String] 'name/version/region'
20
20
  # Value: [Array<String>] 'host:port', 'host:port'
21
21
  sync_cattr_reader :service_registry do
22
- start
22
+ logger.benchmark_info "Connected to Doozer" do
23
+ start
24
+ end
23
25
  end
24
26
 
25
27
  @@on_server_removed_callbacks = ThreadSafe::Hash.new
@@ -91,12 +93,12 @@ module RubySkynet
91
93
  end
92
94
 
93
95
  # Return a server that implements the specified service
94
- def self.server_for(service_name, version='*', region='Development')
95
- if servers = servers_for(service_name, version, region)
96
+ def self.server_for(name, version='*', region='Development')
97
+ if servers = servers_for(name, version, region)
96
98
  # Randomly select one of the servers offering the service
97
99
  servers[rand(servers.size)]
98
100
  else
99
- msg = "No servers available for service: #{service_name} with version: #{version} in region: #{region}"
101
+ msg = "No servers available for service: #{name} with version: #{version} in region: #{region}"
100
102
  logger.warn msg
101
103
  raise ServiceUnavailable.new(msg)
102
104
  end
@@ -105,17 +107,17 @@ module RubySkynet
105
107
  # Returns [Array] of the hostname and port pair [String] that implements a particular service
106
108
  # Performs a doozer lookup to find the servers
107
109
  #
108
- # service_name:
110
+ # name:
109
111
  # Name of the service to lookup
110
112
  # version:
111
113
  # Version of service to locate
112
114
  # Default: All versions
113
115
  # region:
114
116
  # Region to look for the service in
115
- def self.registered_implementers(service_name='*', version='*', region='Development')
117
+ def self.registered_implementers(name='*', version='*', region='Development')
116
118
  hosts = []
117
119
  doozer_pool.with_connection do |doozer|
118
- doozer.walk("/services/#{service_name}/#{version}/#{region}/*/*").each do |node|
120
+ doozer.walk("/services/#{name}/#{version}/#{region}/*/*").each do |node|
119
121
  entry = MultiJson.load(node.value)
120
122
  hosts << entry if entry['Registered']
121
123
  end
@@ -124,18 +126,18 @@ module RubySkynet
124
126
  end
125
127
 
126
128
  # Returns [Array<String>] a list of servers implementing the requested service
127
- def self.servers_for(service_name, version='*', region='Development')
129
+ def self.servers_for(name, version='*', region='Development')
128
130
  if version == '*'
129
131
  # Find the highest version for the named service in this region
130
132
  version = -1
131
133
  service_registry.keys.each do |key|
132
- if match = key.match(/#{service_name}\/(\d+)\/#{region}/)
134
+ if match = key.match(/#{name}\/(\d+)\/#{region}/)
133
135
  ver = match[1].to_i
134
136
  version = ver if ver > version
135
137
  end
136
138
  end
137
139
  end
138
- if server_infos = service_registry["#{service_name}/#{version}/#{region}"]
140
+ if server_infos = service_registry["#{name}/#{version}/#{region}"]
139
141
  server_infos.first.servers
140
142
  end
141
143
  end
@@ -164,7 +166,7 @@ module RubySkynet
164
166
  # 1
165
167
  server_match = IPV4_REG_EXP.match(ip_address) || IPV4_REG_EXP.match(Resolv::DNS.new.getaddress(ip_address).to_s)
166
168
  if server_match
167
- @@local_match ||= IPV4_REG_EXP.match(Common.local_ip_address)
169
+ @@local_match ||= IPV4_REG_EXP.match(RubySkynet.local_ip_address)
168
170
  score = 0
169
171
  (1..4).each do |i|
170
172
  break if @@local_match[i].to_i != server_match[i].to_i
@@ -178,9 +180,7 @@ module RubySkynet
178
180
  protected
179
181
 
180
182
  # Logging instance for this class
181
- sync_cattr_reader :logger do
182
- SemanticLogger::Logger.new(self, :debug)
183
- end
183
+ include SemanticLogger::Loggable
184
184
 
185
185
  # Lazy initialize Doozer Client Connection pool
186
186
  sync_cattr_reader :doozer_pool do
@@ -250,7 +250,7 @@ module RubySkynet
250
250
  # path from doozer: "/services/TutorialService/1/Development/127.0.0.1/9000"
251
251
  e = path.split('/')
252
252
 
253
- # Key: [String] 'service_name/version/region'
253
+ # Key: [String] 'name/version/region'
254
254
  key = "#{e[2]}/#{e[3]}/#{e[4]}"
255
255
  hostname, port = e[5], e[6]
256
256
 
@@ -287,7 +287,7 @@ module RubySkynet
287
287
  ServerInfo = Struct.new(:score, :servers )
288
288
 
289
289
  # Format of the internal services registry
290
- # key: [String] "<service_name>/<version>/<region>"
290
+ # key: [String] "<name>/<version>/<region>"
291
291
  # value: [ServiceInfo, ServiceInfo]
292
292
  # Sorted by highest score first
293
293
 
@@ -353,16 +353,16 @@ module RubySkynet
353
353
 
354
354
  # Check doozer for servers matching supplied criteria
355
355
  # Code unused, consider deleting
356
- def self.remote_servers_for(service_name, version='*', region='Development')
356
+ def self.remote_servers_for(name, version='*', region='Development')
357
357
  if version != '*'
358
- registered_implementers(service_name, version, region).map do |host|
358
+ registered_implementers(name, version, region).map do |host|
359
359
  service = host['Config']['ServiceAddr']
360
360
  "#{service['IPAddress']}:#{service['Port']}"
361
361
  end
362
362
  else
363
363
  # Find the highest version of any particular service
364
364
  versions = {}
365
- registered_implementers(service_name, version, region).each do |host|
365
+ registered_implementers(name, version, region).each do |host|
366
366
  service = host['Config']['ServiceAddr']
367
367
  (versions[version.to_i] ||= []) << "#{service['IPAddress']}:#{service['Port']}"
368
368
  end
@@ -0,0 +1,69 @@
1
+ module RubySkynet
2
+
3
+ # Returns the default region for all Ruby Skynet Clients and Services
4
+ def self.region
5
+ @@region ||= 'Development'
6
+ end
7
+
8
+ # Sets the default region to use for Skynet Clients and Services
9
+ def self.region=(region)
10
+ @@region = region
11
+ end
12
+
13
+ # Returns the service_path where services are located
14
+ def self.services_path
15
+ @@services_path ||= 'app/services'
16
+ end
17
+
18
+ # Sets the service_path where services are located
19
+ def self.services_path=(services_path)
20
+ @@services_path = services_path
21
+ end
22
+
23
+ # Returns the starting port for the server to listen on
24
+ # If this port is in use the next available port will be used
25
+ # upto 999 above the server_port value
26
+ def self.server_port
27
+ @@server_port ||= 2000
28
+ end
29
+
30
+ def self.server_port=(server_port)
31
+ @@server_port = server_port
32
+ end
33
+
34
+ # The ip address at which this server instance can be reached
35
+ # by remote Skynet clients
36
+ # Note: Must be an IP address, not the hostname
37
+ def self.local_ip_address
38
+ @@local_ip_address ||= Common::local_ip_address
39
+ end
40
+
41
+ def self.local_ip_address=(local_ip_address)
42
+ @@local_ip_address = local_ip_address
43
+ end
44
+
45
+ # Load the Encryption Configuration from a YAML file
46
+ # filename:
47
+ # Name of file to read.
48
+ # Mandatory for non-Rails apps
49
+ # Default: Rails.root/config/ruby_skynet.yml
50
+ # environment:
51
+ # Which environment config to load. Usually: production, development, etc.
52
+ # Default: Rails.env
53
+ def self.configure!(filename=nil, environment=nil)
54
+ config_file = filename.nil? ? Rails.root.join('config', 'ruby_skynet.yml') : Pathname.new(filename)
55
+ raise "ruby_skynet config not found. Create a config file at: config/ruby_skynet.yml" unless config_file.file?
56
+
57
+ cfg = YAML.load(ERB.new(File.new(config_file).read).result)[environment || Rails.env]
58
+ raise("Environment #{Rails.env} not defined in config/ruby_skynet.yml") unless cfg
59
+
60
+ RubySkynet.region = cfg.delete(:region) if [:region]
61
+ RubySkynet.services_path = cfg.delete(:services_path) if [:services_path]
62
+ RubySkynet.server_port = cfg.delete(:server_port) if [:server_port]
63
+ RubySkynet.local_ip_address = cfg.delete(:local_ip_address) if [:local_ip_address]
64
+ RubySkynet::Registry.doozer_config = cfg.delete(:doozer) if [:doozer]
65
+
66
+ cfg.each_pair {|k,v| RubySkynet::Server.logger.warn "Ignoring unknown RubySkynet config option #{k} => #{v}"}
67
+ end
68
+
69
+ end
@@ -13,8 +13,14 @@ module RubySkynet
13
13
  @@services = ThreadSafe::Hash.new
14
14
 
15
15
  # Start a single instance of the server
16
- def self.start(region = 'Development', start_port = 2000, hostname = nil)
17
- @@server ||= new(region, start_port, hostname)
16
+ def self.start(start_port = nil, ip_address = nil)
17
+ @@server ||= new(start_port, ip_address)
18
+
19
+ # Stop the skynet server on shutdown
20
+ # To ensure services are de-registered in doozer
21
+ at_exit do
22
+ ::RubySkynet::Server.stop
23
+ end
18
24
  end
19
25
 
20
26
  # Stop the single instance of the server
@@ -28,6 +34,11 @@ module RubySkynet
28
34
  (@@server != nil) && @@server.running?
29
35
  end
30
36
 
37
+ # Wait forever until the running server stops
38
+ def self.wait_until_server_stops
39
+ (@@server != nil) && @@server.wait_until_server_stops
40
+ end
41
+
31
42
  # Services currently loaded and available at this server when running
32
43
  def self.services
33
44
  @@services
@@ -35,39 +46,49 @@ module RubySkynet
35
46
 
36
47
  # Registers a Service Class as being available at this host and port
37
48
  def self.register_service(klass)
38
- raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:service_name) && klass.respond_to?(:service_version)
49
+ raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:skynet_name) && klass.respond_to?(:skynet_version) && klass.respond_to?(:skynet_region)
39
50
 
40
- if previous_klass = @@services[klass.service_name] && (previous_klass.name != klass.name)
41
- logger.warn("Service with name: #{klass.service_name} is already registered to a different implementation:#{previous_klass.name}")
51
+ if previous_klass = @@services[klass.skynet_name] && (previous_klass.name != klass.name)
52
+ logger.warn("Service with name: #{klass.skynet_name} is already registered to a different implementation:#{previous_klass.name}")
42
53
  end
43
- @@services[klass.service_name] = klass
54
+ @@services[klass.skynet_name] = klass
44
55
  @@server.register_service(klass) if @@server
45
56
  end
46
57
 
47
58
  # De-register service
48
59
  def self.deregister_service(klass)
49
- raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:service_name) && klass.respond_to?(:service_version)
60
+ raise InvalidServiceException.new("#{klass.inspect} is not a RubySkynet::Service") unless klass.respond_to?(:skynet_name) && klass.respond_to?(:skynet_version) && klass.respond_to?(:skynet_region)
50
61
 
51
62
  @@server.deregister_service(klass) if @@server
52
- @@services.delete(klass.service_name)
63
+ @@services.delete(klass.skynet_name)
64
+ end
65
+
66
+ # Load and register all services found in the supplied path and it's sub-directories
67
+ def self.load_services
68
+ RubySkynet::Server.logger.benchmark_info "Loaded Skynet Services" do
69
+ # Load services
70
+ Dir.glob("#{RubySkynet.services_path}/**/*.rb").each do |path|
71
+ load path
72
+ end
73
+ end
53
74
  end
54
75
 
55
- # The actual port the server is running at which will be different
56
- # from Server.port if that port was already in use at startup
57
- attr_reader :hostname, :port, :region
76
+ # The actual port the server is running at
77
+ attr_reader :hostname, :port
58
78
 
59
79
  # Start the server so that it can start taking RPC calls
60
80
  # Returns false if the server is already running
61
- def initialize(region = 'Development', start_port = 2000, hostname = nil)
62
- hostname ||= Common.local_ip_address
81
+ def initialize(start_port = nil, ip_address = nil)
82
+ ip_address ||= RubySkynet.local_ip_address
83
+ start_port = (start_port || RubySkynet.server_port).to_i
84
+ raise InvalidConfigurationException.new("Invalid Starting Port number: #{start_port}") unless start_port > 0
63
85
 
64
86
  # If port is in use, try the next port in sequence
65
87
  port_count = 0
66
88
  begin
67
- @server = ::TCPServer.new(hostname, start_port + port_count)
68
- @hostname = hostname
89
+ @server = ::TCPServer.new(ip_address, start_port + port_count)
90
+ @hostname = ip_address
69
91
  @port = start_port + port_count
70
- @region = region
71
92
  rescue Errno::EADDRINUSE => exc
72
93
  if port_count < 999
73
94
  port_count += 1
@@ -77,7 +98,7 @@ module RubySkynet
77
98
  end
78
99
 
79
100
  # Start Server listener thread
80
- Thread.new { run }
101
+ @listener_thread = Thread.new { run }
81
102
 
82
103
  # Register services hosted by this server
83
104
  self.class.services.each_value {|klass| register_service(klass)}
@@ -94,6 +115,19 @@ module RubySkynet
94
115
  logger.info "Skynet Services De-registered"
95
116
  end
96
117
 
118
+ # Returns whether the server is running
119
+ def running?
120
+ (@server != nil) && !@server.closed?
121
+ end
122
+
123
+ # Wait forever until the running server stops
124
+ def wait_until_server_stops
125
+ @listener_thread.join
126
+ end
127
+
128
+ ############################################################################
129
+ protected
130
+
97
131
  def run
98
132
  logger.info("Starting listener on #{hostname}:#{port}")
99
133
  loop do
@@ -129,22 +163,22 @@ module RubySkynet
129
163
  logger.debug "Received Request"
130
164
  logger.trace 'Header', header
131
165
 
132
- service_name = header['servicemethod'] # "#{service_name}.Forward",
133
- raise "Invalid Skynet RPC Request, missing servicemethod" unless service_name
134
- match = service_name.match /(.*)\.Forward$/
166
+ name = header['servicemethod']
167
+ raise "Invalid Skynet RPC Request, missing servicemethod" unless name
168
+ match = name.match /(.*)\.Forward$/
135
169
  raise "Invalid Skynet RPC Request, servicemethod must end with '.Forward'" unless match
136
- service_name = match[1]
170
+ name = match[1]
137
171
 
138
172
  request = Common.read_bson_document(client)
139
173
  logger.trace 'Request', request
140
174
  break unless request
141
175
  params = BSON.deserialize(request['in'])
142
176
  logger.trace 'Parameters', params
143
-
177
+
144
178
  reply = begin
145
- on_message(service_name, request['method'].to_sym, params)
179
+ on_message(name, request['method'].to_sym, params)
146
180
  rescue ScriptError, NameError, StandardError, Exception => exc
147
- logger.error "Exception while calling service: #{service_name}", exc
181
+ logger.error "Exception while calling service: #{name}", exc
148
182
  { :exception => {:message => exc.message, :class => exc.class.name} }
149
183
  end
150
184
 
@@ -169,36 +203,28 @@ module RubySkynet
169
203
  logger.debug "Disconnected from the client"
170
204
  end
171
205
 
172
- # Returns whether the server is running
173
- def running?
174
- (@server != nil) && !@server.closed?
175
- end
176
-
177
- ############################################################################
178
- protected
179
-
180
206
  # Registers a Service Class as being available at this server
181
207
  def register_service(klass)
182
- logger.debug "Registering Service: #{klass.name} with name: #{klass.service_name}"
183
- Registry.register_service(klass.service_name, klass.service_version, @region, @hostname, @port)
208
+ logger.debug "Registering Service: #{klass.name} with name: #{klass.skynet_name}"
209
+ Registry.register_service(klass.skynet_name, klass.skynet_version || 1, klass.skynet_region, @hostname, @port)
184
210
  end
185
211
 
186
212
  # De-register service from this server
187
213
  def deregister_service(klass)
188
- logger.debug "De-registering Service: #{klass.name} with name: #{klass.service_name}"
189
- Registry.deregister_service(klass.service_name, klass.service_version, @region, @hostname, @port)
214
+ logger.debug "De-registering Service: #{klass.name} with name: #{klass.skynet_name}"
215
+ Registry.deregister_service(klass.skynet_name, klass.skynet_version || 1, klass.skynet_region, @hostname, @port)
190
216
  end
191
217
 
192
218
  # Called for each message received from the client
193
219
  # Returns a Hash that is sent back to the caller
194
- def on_message(service_name, method, params)
195
- logger.benchmark_info("Skynet Call: #{service_name}##{method}") do
220
+ def on_message(skynet_name, method, params)
221
+ logger.benchmark_info("Skynet Call: #{skynet_name}##{method}") do
196
222
  logger.trace "Method Call: #{method} with parameters:", params
197
- klass = Server.services[service_name]
198
- raise "Invalid Skynet RPC call, service: #{service_name} is not available at this server" unless klass
223
+ klass = Server.services[skynet_name]
224
+ raise "Invalid Skynet RPC call, service: #{skynet_name} is not available at this server" unless klass
199
225
  # TODO Use pool of services
200
226
  service = klass.new
201
- raise "Invalid Skynet RPC call, method: #{method} does not exist for service: #{service_name}" unless service.respond_to?(method)
227
+ raise "Invalid Skynet RPC call, method: #{method} does not exist for service: #{skynet_name}" unless service.respond_to?(method)
202
228
  service.send(method, params)
203
229
  end
204
230
  end
@@ -1,6 +1,4 @@
1
- # Doozer entries are in json
2
- require 'multi_json'
3
- require 'thread_safe'
1
+ require 'semantic_logger'
4
2
 
5
3
  #
6
4
  # RubySkynet Service
@@ -11,43 +9,17 @@ require 'thread_safe'
11
9
  #
12
10
  module RubySkynet
13
11
  module Service
12
+
14
13
  def self.included(base)
15
- base.extend ClassMethods
14
+ base.extend ::RubySkynet::Base::ClassMethods
16
15
  base.class_eval do
17
16
  include SemanticLogger::Loggable
18
-
19
- sync_cattr_reader :logger do
20
- SemanticLogger::Logger.new(self)
21
- end
22
17
  end
23
18
  # Register the service with the Server
24
19
  # The server will publish the server to Doozer when the server is running
25
20
  Server.register_service(base)
26
21
  end
27
22
 
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
- end
50
-
51
23
  end
52
24
  end
53
25
 
@@ -1,3 +1,3 @@
1
1
  module RubySkynet #:nodoc
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/ruby_skynet.rb CHANGED
@@ -1,14 +1,20 @@
1
1
  require 'semantic_logger'
2
2
  require 'ruby_skynet/exceptions'
3
3
  require 'ruby_skynet/version'
4
+ require 'ruby_skynet/ruby_skynet'
4
5
  module RubySkynet
5
6
  module Doozer
6
7
  autoload :Client, 'ruby_skynet/doozer/client'
7
8
  end
8
9
  autoload :Registry, 'ruby_skynet/registry'
9
10
  autoload :Connection, 'ruby_skynet/connection'
11
+ autoload :Base, 'ruby_skynet/base'
10
12
  autoload :Common, 'ruby_skynet/common'
11
13
  autoload :Client, 'ruby_skynet/client'
12
14
  autoload :Service, 'ruby_skynet/service'
13
15
  autoload :Server, 'ruby_skynet/server'
14
16
  end
17
+
18
+ if defined?(Rails)
19
+ require 'ruby_skynet/railtie'
20
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,89 @@
1
+ # Allow test to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+ $LOAD_PATH.unshift File.dirname(__FILE__)
4
+
5
+ require 'rubygems'
6
+ require 'test/unit'
7
+ require 'shoulda'
8
+ require 'ruby_skynet'
9
+
10
+ # Register an appender if one is not already registered
11
+ if SemanticLogger::Logger.appenders.size == 0
12
+ SemanticLogger::Logger.default_level = :debug
13
+ SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('test.log')
14
+ end
15
+
16
+ # Service implementation
17
+ class BaseTestService
18
+ include RubySkynet::Service
19
+
20
+ # Methods implemented by this service
21
+ # Must take a Hash as input
22
+ # Must Return a Hash response or nil for no response
23
+ def test1(params)
24
+ { 'result' => 'test1' }
25
+ end
26
+
27
+ def sleep(params)
28
+ Kernel.sleep params['duration'] || 1
29
+ { 'result' => 'sleep' }
30
+ end
31
+
32
+ def fail(params)
33
+ if params['attempt'].to_i >= 2
34
+ { 'result' => 'fail' }
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ # Service Client
43
+ class BaseTestServiceClient
44
+ include RubySkynet::Base
45
+
46
+ # Override Name registered in skynet to match server above
47
+ self.skynet_name = 'BaseTestService'
48
+ end
49
+
50
+ # Unit Test
51
+ class BaseTest < Test::Unit::TestCase
52
+ context RubySkynet::Base do
53
+
54
+ context "with server" do
55
+ setup do
56
+ RubySkynet.region = @region
57
+ RubySkynet::Server.start
58
+
59
+ @read_timeout = 3.0
60
+ end
61
+
62
+ teardown do
63
+ RubySkynet::Server.stop
64
+ end
65
+
66
+ context "with client connection" do
67
+ setup do
68
+ @client = BaseTestServiceClient.new
69
+ end
70
+
71
+ should "successfully send and receive data" do
72
+ reply = @client.test1('some' => 'parameters')
73
+ assert_equal 'test1', reply['result']
74
+ end
75
+
76
+ should "timeout on receive" do
77
+ request = { 'duration' => @read_timeout + 0.5}
78
+
79
+ exception = assert_raise ResilientSocket::ReadTimeout do
80
+ # Read 4 bytes from server
81
+ @client.sleep(request, :read_timeout => @read_timeout)
82
+ end
83
+ assert_match /Timedout after #{@read_timeout} seconds trying to read/, exception.message
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
data/test/client_test.rb CHANGED
@@ -56,7 +56,8 @@ class ClientTest < Test::Unit::TestCase
56
56
  context "with server" do
57
57
  setup do
58
58
  @region = 'ClientTest'
59
- RubySkynet::Server.start(@region)
59
+ RubySkynet.region = @region
60
+ RubySkynet::Server.start
60
61
 
61
62
  @service_name = 'ClientTestService'
62
63
  @version = 1
@@ -89,7 +89,7 @@ class RegistryTest < Test::Unit::TestCase
89
89
  ['10.0.11.0', 0 ],
90
90
  ].each do |test|
91
91
  should "handle score #{test[1]}" do
92
- RubySkynet::Common.stubs(:local_ip_address).returns("192.168.11.0")
92
+ RubySkynet.stubs(:local_ip_address).returns("192.168.11.0")
93
93
  assert_equal test[1], RubySkynet::Registry.score_for_server(test[0]), "Local: #{RubySkynet::Common.local_ip_address} Server: #{test[0]} Score: #{test[1]}"
94
94
  end
95
95
  end
data/test/service_test.rb CHANGED
@@ -35,7 +35,8 @@ class ServiceTest < Test::Unit::TestCase
35
35
  @region = 'Test'
36
36
  @service_name = 'TestService'
37
37
  @version = 1
38
- RubySkynet::Server.start(@region)
38
+ RubySkynet.region = @region
39
+ RubySkynet::Server.start
39
40
  sleep 0.2
40
41
  end
41
42
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_skynet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-22 00:00:00.000000000 Z
11
+ date: 2013-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic_logger
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: gene_pool
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  description: Ruby Client for invoking Skynet services
84
98
  email:
85
99
  - reidmo@gmail.com
@@ -95,7 +109,10 @@ files:
95
109
  - examples/e.rb
96
110
  - examples/echo_client.rb
97
111
  - examples/echo_server.rb
112
+ - lib/rails/generators/ruby_skynet/config/config_generator.rb
113
+ - lib/rails/generators/ruby_skynet/config/templates/ruby_skynet.yml
98
114
  - lib/ruby_skynet.rb
115
+ - lib/ruby_skynet/base.rb
99
116
  - lib/ruby_skynet/client.rb
100
117
  - lib/ruby_skynet/common.rb
101
118
  - lib/ruby_skynet/connection.rb
@@ -103,11 +120,15 @@ files:
103
120
  - lib/ruby_skynet/doozer/exceptions.rb
104
121
  - lib/ruby_skynet/doozer/msg.pb.rb
105
122
  - lib/ruby_skynet/exceptions.rb
123
+ - lib/ruby_skynet/railtie.rb
124
+ - lib/ruby_skynet/railties/ruby_skynet.rake
106
125
  - lib/ruby_skynet/registry.rb
126
+ - lib/ruby_skynet/ruby_skynet.rb
107
127
  - lib/ruby_skynet/server.rb
108
128
  - lib/ruby_skynet/service.rb
109
129
  - lib/ruby_skynet/version.rb
110
130
  - test.sh
131
+ - test/base_test.rb
111
132
  - test/client_test.rb
112
133
  - test/doozer_client_test.rb
113
134
  - test/registry_test.rb