ruby_skynet 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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