sanford 0.10.1 → 0.11.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.
Files changed (73) hide show
  1. data/Gemfile +1 -1
  2. data/README.md +41 -56
  3. data/Rakefile +0 -1
  4. data/bench/client.rb +8 -3
  5. data/bench/{services.rb → config.sanford} +11 -6
  6. data/bench/{runner.rb → report.rb} +2 -2
  7. data/bench/report.txt +32 -32
  8. data/lib/sanford/cli.rb +42 -28
  9. data/lib/sanford/config_file.rb +79 -0
  10. data/lib/sanford/{worker.rb → connection_handler.rb} +28 -20
  11. data/lib/sanford/error_handler.rb +7 -7
  12. data/lib/sanford/pid_file.rb +42 -0
  13. data/lib/sanford/process.rb +136 -0
  14. data/lib/sanford/process_signal.rb +20 -0
  15. data/lib/sanford/route.rb +48 -0
  16. data/lib/sanford/router.rb +36 -0
  17. data/lib/sanford/runner.rb +30 -58
  18. data/lib/sanford/sanford_runner.rb +19 -9
  19. data/lib/sanford/server.rb +211 -42
  20. data/lib/sanford/server_data.rb +47 -0
  21. data/lib/sanford/service_handler.rb +8 -46
  22. data/lib/sanford/template_source.rb +19 -2
  23. data/lib/sanford/test_runner.rb +27 -28
  24. data/lib/sanford/version.rb +1 -1
  25. data/lib/sanford.rb +1 -23
  26. data/sanford.gemspec +4 -5
  27. data/test/helper.rb +3 -20
  28. data/test/support/app_server.rb +142 -0
  29. data/test/support/config.sanford +7 -0
  30. data/test/support/config_invalid_run.sanford +3 -0
  31. data/test/support/config_no_run.sanford +0 -0
  32. data/test/support/fake_server_connection.rb +58 -0
  33. data/test/support/pid_file_spy.rb +19 -0
  34. data/test/support/template.erb +1 -0
  35. data/test/system/server_tests.rb +378 -0
  36. data/test/system/service_handler_tests.rb +224 -0
  37. data/test/unit/cli_tests.rb +187 -0
  38. data/test/unit/config_file_tests.rb +59 -0
  39. data/test/unit/connection_handler_tests.rb +254 -0
  40. data/test/unit/error_handler_tests.rb +30 -35
  41. data/test/unit/pid_file_tests.rb +70 -0
  42. data/test/unit/process_signal_tests.rb +61 -0
  43. data/test/unit/process_tests.rb +428 -0
  44. data/test/unit/route_tests.rb +92 -0
  45. data/test/unit/router_tests.rb +65 -0
  46. data/test/unit/runner_tests.rb +61 -15
  47. data/test/unit/sanford_runner_tests.rb +162 -28
  48. data/test/unit/sanford_tests.rb +0 -8
  49. data/test/unit/server_data_tests.rb +87 -0
  50. data/test/unit/server_tests.rb +502 -21
  51. data/test/unit/service_handler_tests.rb +114 -219
  52. data/test/unit/template_engine_tests.rb +1 -1
  53. data/test/unit/template_source_tests.rb +56 -16
  54. data/test/unit/test_runner_tests.rb +206 -0
  55. metadata +67 -67
  56. data/bench/tasks.rb +0 -41
  57. data/lib/sanford/config.rb +0 -28
  58. data/lib/sanford/host.rb +0 -129
  59. data/lib/sanford/host_data.rb +0 -65
  60. data/lib/sanford/hosts.rb +0 -38
  61. data/lib/sanford/manager.rb +0 -275
  62. data/test/support/fake_connection.rb +0 -36
  63. data/test/support/helpers.rb +0 -17
  64. data/test/support/service_handlers.rb +0 -154
  65. data/test/support/services.rb +0 -123
  66. data/test/support/simple_client.rb +0 -62
  67. data/test/system/request_handling_tests.rb +0 -306
  68. data/test/unit/config_tests.rb +0 -56
  69. data/test/unit/host_data_tests.rb +0 -71
  70. data/test/unit/host_tests.rb +0 -141
  71. data/test/unit/hosts_tests.rb +0 -50
  72. data/test/unit/manager_tests.rb +0 -195
  73. data/test/unit/worker_tests.rb +0 -24
@@ -1,65 +0,0 @@
1
- require 'sanford/service_handler'
2
- require 'sanford/sanford_runner'
3
-
4
- module Sanford
5
-
6
- class HostData
7
-
8
- # When trying to run a server for a host, we need to build up the host's
9
- # data to increase the performance of the server. This is done by
10
- # constantizing a host's handlers and merging a host's configuration with
11
- # optional overrides.
12
-
13
- # NOTE: The `name` attribute shouldn't be removed, it is used to identify
14
- # a `HostData`, particularly in error handlers
15
-
16
- attr_reader :name, :logger, :verbose, :keep_alive, :error_procs
17
-
18
- def initialize(service_host, options = nil)
19
- service_host.configuration.init_procs.each(&:call)
20
-
21
- overrides = self.remove_nil_values(options || {})
22
- configuration = service_host.configuration.to_hash.merge(overrides)
23
-
24
- @name = configuration[:name]
25
- @logger = configuration[:logger]
26
- @verbose = configuration[:verbose_logging]
27
- @keep_alive = configuration[:receives_keep_alive]
28
- @error_procs = configuration[:error_procs]
29
-
30
- @handlers = service_host.services.inject({}) do |h, (name, handler_class_name)|
31
- h.merge({ name => self.constantize(handler_class_name) })
32
- end
33
- end
34
-
35
- def handler_class_for(service)
36
- @handlers[service] || raise(Sanford::NotFoundError)
37
- end
38
-
39
- def run(handler_class, request)
40
- SanfordRunner.new(handler_class, request, self.logger).run
41
- end
42
-
43
- protected
44
-
45
- def constantize(handler_class_name)
46
- Sanford::ServiceHandler.constantize(handler_class_name) ||
47
- raise(Sanford::NoHandlerClassError.new(handler_class_name))
48
- end
49
-
50
- def remove_nil_values(hash)
51
- hash.inject({}){|h, (k, v)| !v.nil? ? h.merge({ k => v }) : h }
52
- end
53
-
54
- end
55
-
56
- NotFoundError = Class.new(RuntimeError)
57
-
58
- class NoHandlerClassError < RuntimeError
59
- def initialize(handler_class_name)
60
- super "Sanford couldn't find the service handler '#{handler_class_name}'."\
61
- " It doesn't exist or hasn't been required in yet."
62
- end
63
- end
64
-
65
- end
data/lib/sanford/hosts.rb DELETED
@@ -1,38 +0,0 @@
1
- require 'set'
2
- require 'sanford/host'
3
-
4
- module Sanford
5
-
6
- class Hosts
7
-
8
- def initialize(values = [])
9
- @set = Set.new(values)
10
- end
11
-
12
- def method_missing(method, *args, &block)
13
- @set.send(method, *args, &block)
14
- end
15
-
16
- def respond_to?(method)
17
- super || @set.respond_to?(method)
18
- end
19
-
20
- # We want class names to take precedence over a configured name, so that if
21
- # a user specifies a specific class, they always get it
22
- def find(name)
23
- find_by_class_name(name) || find_by_name(name)
24
- end
25
-
26
- private
27
-
28
- def find_by_class_name(class_name)
29
- @set.detect{|host_class| host_class.to_s == class_name.to_s }
30
- end
31
-
32
- def find_by_name(name)
33
- @set.detect{|host_class| host_class.name == name.to_s }
34
- end
35
-
36
- end
37
-
38
- end
@@ -1,275 +0,0 @@
1
- require 'sanford/cli'
2
- require 'sanford/server'
3
-
4
- module Sanford
5
-
6
- module Manager
7
-
8
- def self.call(action, options = nil)
9
- get_handler_class(action).new(options).tap{ |manager| manager.send(action) }
10
- end
11
-
12
- def self.get_handler_class(action)
13
- case action.to_sym
14
- when :start, :run
15
- ServerHandler
16
- when :stop, :restart
17
- SignalHandler
18
- end
19
- end
20
-
21
- class Config
22
- attr_reader :host_name, :host, :ip, :port, :pid, :pid_file, :restart_dir
23
- attr_reader :file_descriptor, :client_file_descriptors
24
-
25
- def initialize(opts = nil)
26
- options = OpenStruct.new(opts || {})
27
- @host_name = ENV['SANFORD_HOST'] || options.host
28
-
29
- @host = @host_name ? Sanford.hosts.find(@host_name) : Sanford.hosts.first
30
- @host ||= NullHost.new
31
-
32
- @file_descriptor = ENV['SANFORD_SERVER_FD'] || options.file_descriptor
33
- @file_descriptor = @file_descriptor.to_i if @file_descriptor
34
- @ip = ENV['SANFORD_IP'] || options.ip || @host.ip
35
- @port = ENV['SANFORD_PORT'] || options.port || @host.port
36
- @port = @port.to_i if @port
37
-
38
- client_fds_str = ENV['SANFORD_CLIENT_FDS'] || options.client_fds || ""
39
- @client_file_descriptors = client_fds_str.split(',').map(&:to_i)
40
-
41
- @pid_file = PIDFile.new(ENV['SANFORD_PID_FILE'] || options.pid_file || @host.pid_file)
42
- @pid = options.pid || @pid_file.pid
43
-
44
- @restart_dir = ENV['SANFORD_RESTART_DIR'] || options.restart_dir
45
- end
46
-
47
- def listen_args
48
- @file_descriptor ? [ @file_descriptor ] : [ @ip, @port ]
49
- end
50
-
51
- def has_listen_args?
52
- !!@file_descriptor || !!(@ip && @port)
53
- end
54
-
55
- def found_host?
56
- !@host.kind_of?(NullHost)
57
- end
58
-
59
- class NullHost
60
- [ :ip, :port, :pid_file ].each do |method_name|
61
- define_method(method_name){ }
62
- end
63
- end
64
-
65
- class PIDFile
66
- DEF_FILE = '/dev/null'
67
-
68
- def initialize(path)
69
- @path = (path || DEF_FILE).to_s
70
- end
71
-
72
- def pid
73
- pid = File.read(@path).strip if File.exists?(@path)
74
- pid.to_i if pid && !pid.empty?
75
- end
76
-
77
- def write
78
- begin
79
- File.open(@path, 'w'){|f| f.puts Process.pid }
80
- rescue Errno::ENOENT => err
81
- e = RuntimeError.new("Can't write pid to file `#{@path}`")
82
- e.set_backtrace(err.backtrace)
83
- raise e
84
- end
85
- end
86
-
87
- def remove
88
- FileUtils.rm_f(@path)
89
- end
90
-
91
- def to_s
92
- @path
93
- end
94
- end
95
-
96
- end
97
-
98
- class ServerHandler
99
-
100
- def initialize(options = nil)
101
- @config = Config.new(options)
102
- raise Sanford::NoHostError.new(@config.host_name) if !@config.found_host?
103
- raise Sanford::InvalidHostError.new(@config.host) if !@config.has_listen_args?
104
- @host = @config.host
105
- @logger = @host.logger
106
-
107
- @server_options = {}
108
- # FUTURE allow passing through dat-tcp options (min/max workers)
109
- # FUTURE merge in host options for verbose / keep_alive
110
-
111
- @restart_cmd = RestartCmd.new(@config)
112
- end
113
-
114
- def run
115
- self.run! false
116
- end
117
-
118
- def start
119
- self.run! true
120
- end
121
-
122
- protected
123
-
124
- def run!(daemonize = false)
125
- daemonize!(true) if daemonize && !ENV['SANFORD_SKIP_DAEMONIZE']
126
- Sanford::Server.new(@host, @server_options).tap do |server|
127
- log "Starting #{@host.name} server..."
128
-
129
- server.listen(*@config.listen_args)
130
- $0 = ProcessName.new(@host.name, server.ip, server.port)
131
- log "Listening on #{server.ip}:#{server.port}"
132
-
133
- @config.pid_file.write
134
- log "PID: #{Process.pid}"
135
-
136
- Signal.trap("TERM"){ self.stop!(server) }
137
- Signal.trap("INT"){ self.halt!(server) }
138
- Signal.trap("USR2"){ self.restart!(server) }
139
-
140
- server_thread = server.run(@config.client_file_descriptors)
141
- log "#{@host.name} server started and ready."
142
- server_thread.join
143
- end
144
- rescue RuntimeError => err
145
- log "Error: #{err.message}"
146
- log "#{@host.name} server never started."
147
- ensure
148
- @config.pid_file.remove
149
- end
150
-
151
- def restart!(server)
152
- log "Restarting #{@host.name} server..."
153
- server.pause
154
- log "server paused"
155
-
156
- ENV['SANFORD_HOST'] = @host.name
157
- ENV['SANFORD_SERVER_FD'] = server.file_descriptor.to_s
158
- ENV['SANFORD_CLIENT_FDS'] = server.client_file_descriptors.join(',')
159
- ENV['SANFORD_SKIP_DAEMONIZE'] = 'yes'
160
-
161
- log "calling exec ..."
162
- Dir.chdir @restart_cmd.dir
163
- Kernel.exec(*@restart_cmd.argv)
164
- end
165
-
166
- def stop!(server)
167
- log "Stopping #{@host.name} server..."
168
- server.stop
169
- log "#{@host.name} server stopped."
170
- end
171
-
172
- def halt!(server)
173
- log "Halting #{@host.name} server..."
174
- server.halt false
175
- log "#{@host.name} server halted."
176
- end
177
-
178
- # Full explanation: http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
179
- def daemonize!(no_chdir = false, no_close = false)
180
- exit if fork
181
- Process.setsid
182
- exit if fork
183
- Dir.chdir "/" unless no_chdir
184
- if !no_close
185
- null = File.open "/dev/null", 'w'
186
- STDIN.reopen null
187
- STDOUT.reopen null
188
- STDERR.reopen null
189
- end
190
- return 0
191
- end
192
-
193
- def log(message)
194
- @logger.info "[Sanford] #{message}"
195
- end
196
-
197
- class ProcessName < String
198
- def initialize(name, ip, port)
199
- super "#{[ name, ip, port ].join('_')}"
200
- end
201
- end
202
-
203
- class RestartCmd
204
- attr_reader :argv, :dir
205
-
206
- def initialize(config = nil)
207
- require 'rubygems'
208
- config ||= OpenStruct.new
209
- @dir = config.restart_dir || get_pwd
210
- @argv = [ Gem.ruby, $0, ARGV.dup ].flatten
211
- end
212
-
213
- protected
214
-
215
- # Trick from puma/unicorn. Favor PWD because it contains an unresolved
216
- # symlink. This is useful when restarting after deploying; the original
217
- # directory may be removed, but the symlink is pointing to a new
218
- # directory.
219
- def get_pwd
220
- env_stat = File.stat(ENV['PWD'])
221
- pwd_stat = File.stat(Dir.pwd)
222
- if env_stat.ino == pwd_stat.ino && env_stat.dev == pwd_stat.dev
223
- ENV['PWD']
224
- else
225
- Dir.pwd
226
- end
227
- end
228
-
229
- end
230
-
231
- end
232
-
233
- class SignalHandler
234
-
235
- def initialize(options = nil)
236
- @config = Config.new(options)
237
- raise Sanford::NoPIDError.new if !@config.pid
238
- end
239
-
240
- def stop
241
- Process.kill("TERM", @config.pid)
242
- end
243
-
244
- def restart
245
- Process.kill("USR2", @config.pid)
246
- end
247
-
248
- end
249
-
250
- end
251
-
252
- class NoHostError < CLIRB::Error
253
- def initialize(host_name)
254
- message = if Sanford.hosts.empty?
255
- "No hosts have been defined. Please define a host before trying to run Sanford."
256
- else
257
- "A host couldn't be found with the name #{host_name.inspect}. "
258
- end
259
- super message
260
- end
261
- end
262
-
263
- class InvalidHostError < CLIRB::Error
264
- def initialize(host)
265
- super "A port must be configured or provided to run a server for '#{host}'"
266
- end
267
- end
268
-
269
- class NoPIDError < CLIRB::Error
270
- def initialize
271
- super "A PID or PID file is required"
272
- end
273
- end
274
-
275
- end
@@ -1,36 +0,0 @@
1
- class FakeConnection
2
-
3
- attr_reader :read_data, :response, :write_stream_closed
4
-
5
- def self.with_request(name, params = {}, raise_on_write = false)
6
- request = Sanford::Protocol::Request.new(name, params)
7
- self.new(request.to_hash, raise_on_write)
8
- end
9
-
10
- def initialize(*args)
11
- if args.first.kind_of?(Sanford::Protocol::Connection)
12
- protocol_connection = args.first
13
- @read_data = proc{ protocol_connection.read }
14
- @write_data = proc{|data| protocol_connection.write(data) }
15
- else
16
- @read_data, @raise_on_write = args
17
- end
18
- end
19
-
20
- def read_data
21
- @read_data.kind_of?(Proc) ? @read_data.call : @read_data
22
- end
23
-
24
- def write_data(data)
25
- if @raise_on_write
26
- @raise_on_write = false
27
- raise 'test fail'
28
- end
29
- @response = Sanford::Protocol::Response.parse(data)
30
- end
31
-
32
- def close_write
33
- @write_stream_closed = true
34
- end
35
-
36
- end
@@ -1,17 +0,0 @@
1
- module Test
2
- module SpawnServerHelper
3
-
4
- def start_server(host, &block)
5
- begin
6
- server = Sanford::Server.new(host, { :ready_timeout => 0.1 })
7
- server.listen(host.ip, host.port)
8
- thread = server.run
9
- yield
10
- ensure
11
- server.halt if server
12
- thread.join if thread
13
- end
14
- end
15
-
16
- end
17
- end
@@ -1,154 +0,0 @@
1
- class BasicServiceHandler
2
- include Sanford::ServiceHandler
3
-
4
- def run!
5
- { 'name' => 'Joe Test', 'email' => "joe.test@example.com" }
6
- end
7
-
8
- end
9
-
10
- class SerializeErrorServiceHandler
11
- include Sanford::ServiceHandler
12
-
13
- # return data that fails BSON serialization
14
- # BSON errors if it is sent date/datetime values
15
- def run!
16
- { 'date' => Date.today,
17
- 'datetime' => DateTime.now
18
- }
19
- end
20
-
21
- end
22
-
23
- module CallbackServiceHandler
24
-
25
- def self.included(receiver)
26
- receiver.class_eval do
27
- attr_reader :before_called, :after_called
28
- attr_reader :before_init_called, :init_bang_called, :after_init_called
29
- attr_reader :before_run_called, :run_bang_called, :after_run_called
30
- attr_reader :second_before_init_called, :second_after_run_called
31
-
32
- before do
33
- @before_called = true
34
- end
35
- after do
36
- @after_called = true
37
- end
38
-
39
- before_init do
40
- @before_init_called = true
41
- end
42
- before_init do
43
- @second_before_init_called = true
44
- end
45
-
46
- after_init do
47
- @after_init_called = true
48
- end
49
-
50
- before_run do
51
- @before_run_called = true
52
- end
53
-
54
- after_run do
55
- @after_run_called = true
56
- end
57
- after_run do
58
- @second_after_run_called = true
59
- end
60
-
61
- end
62
-
63
- end
64
-
65
- def init!
66
- @init_bang_called = true
67
- end
68
-
69
- def run!
70
- @run_bang_called = true
71
- end
72
-
73
- end
74
-
75
- class FlagServiceHandler
76
- include Sanford::ServiceHandler
77
- include CallbackServiceHandler
78
-
79
- end
80
-
81
- class HaltingBehaviorServiceHandler
82
- include Sanford::ServiceHandler
83
- include CallbackServiceHandler
84
-
85
- before_init do
86
- halt_when('before_init')
87
- end
88
-
89
- def init!
90
- super
91
- halt_when('init!')
92
- end
93
-
94
- after_init do
95
- halt_when('after_init')
96
- end
97
-
98
- before_run do
99
- halt_when('before_run')
100
- end
101
-
102
- def run!
103
- super
104
- halt_when('run!')
105
- end
106
-
107
- after_run do
108
- halt_when('after_run')
109
- end
110
-
111
- def halt_when(method_name)
112
- return if ![*params['when']].include?(method_name)
113
- halt(200, {
114
- :message => "#{method_name} halting",
115
- :data => {
116
- :before_init_called => @before_init_called,
117
- :init_bang_called => @init_bang_called,
118
- :after_init_called => @after_init_called,
119
- :before_run_called => @before_run_called,
120
- :run_bang_called => @run_bang_called,
121
- :after_run_called => @after_run_called
122
- }
123
- })
124
- end
125
-
126
- end
127
-
128
- class RenderHandler
129
- include Sanford::ServiceHandler
130
-
131
- def run!
132
- render params['template_name']
133
- end
134
- end
135
-
136
- class RunOtherHandler
137
- include Sanford::ServiceHandler
138
-
139
- def run!
140
- response = run_handler(HaltServiceHandler, 'code' => 200, 'data' => 'RunOtherHandler')
141
- response.data
142
- end
143
- end
144
-
145
- class HaltServiceHandler
146
- include Sanford::ServiceHandler
147
-
148
- def run!
149
- halt params['code'], :message => params['message'], :data => params['data']
150
- end
151
-
152
- end
153
-
154
- class InvalidServiceHandler; end
@@ -1,123 +0,0 @@
1
- require 'logger'
2
-
3
- class TestHost
4
- include Sanford::Host
5
-
6
- attr_accessor :init_has_been_called
7
-
8
- init do
9
- self.init_has_been_called = true
10
- end
11
-
12
- ip 'localhost'
13
- port 12000
14
- pid_file File.expand_path('../../../tmp/test_host.pid', __FILE__)
15
-
16
- logger(Logger.new(File.expand_path("../../../log/test.log", __FILE__)).tap do |logger|
17
- logger.level = Logger::DEBUG
18
- end)
19
- verbose_logging false
20
-
21
- error do |exception, host_data, request|
22
- if exception.kind_of?(::MyCustomError)
23
- Sanford::Protocol::Response.new([ 987, 'custom error!' ])
24
- end
25
- end
26
-
27
- service_handler_ns 'TestHost'
28
-
29
- service :echo, 'Echo'
30
- service 'bad', 'Bad'
31
- service 'multiply', 'Multiply'
32
- service 'halt_it', '::TestHost::HaltIt'
33
- service 'authorized', 'Authorized'
34
- service 'custom_error', 'CustomError'
35
-
36
- class Echo
37
- include Sanford::ServiceHandler
38
-
39
- def run!
40
- params['message']
41
- end
42
-
43
- end
44
-
45
- class Bad
46
- include Sanford::ServiceHandler
47
-
48
- def run!
49
- raise "hahaha"
50
- end
51
- end
52
-
53
- class Multiply
54
- include Sanford::ServiceHandler
55
-
56
- def init!
57
- @number = params['number'] || 1
58
- end
59
-
60
- def run!
61
- @number * 2
62
- end
63
- end
64
-
65
- class HaltIt
66
- include Sanford::ServiceHandler
67
-
68
- def run!
69
- halt 728, {
70
- :message => "I do what I want",
71
- :data => [ 1, true, 'yes' ]
72
- }
73
- end
74
- end
75
-
76
- class Authorized
77
- include Sanford::ServiceHandler
78
-
79
- before_run do
80
- halt 401, :message => "Not authorized"
81
- end
82
-
83
- end
84
-
85
- ::MyCustomError = Class.new(RuntimeError)
86
-
87
- class CustomError
88
- include Sanford::ServiceHandler
89
-
90
- def run!
91
- raise ::MyCustomError
92
- end
93
-
94
- end
95
-
96
- end
97
-
98
- class MyHost
99
- include Sanford::Host
100
-
101
- name 'my_host'
102
- ip 'my.local'
103
- pid_file File.expand_path('../../../tmp/my_host.pid', __FILE__)
104
- end
105
-
106
- class InvalidHost
107
- include Sanford::Host
108
-
109
- name 'invalid_host'
110
- end
111
-
112
- class UndefinedHandlersHost
113
- include Sanford::Host
114
-
115
- port 12345
116
-
117
- service 'undefined', 'ThisIsNotDefined'
118
-
119
- end
120
-
121
- class EmptyHost
122
- include Sanford::Host
123
- end