sanford 0.10.1 → 0.11.0

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