sanford 0.8.0 → 0.9.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9e76d785a8dc8ecf399b69798c6671ea3cc59661
4
+ data.tar.gz: 9ffe75e092d9672b18f796e3702751f3b7b8f4dd
5
+ SHA512:
6
+ metadata.gz: 8ab100e15c7772f82d3a5aebb52418c80af556596cc186b13a7559b99fcc02085fdb91c2504ef637957bedae4e661e9248ebf7ea25b3eed74fc0630816689651
7
+ data.tar.gz: fa68f9c299344ef106bd52c836c7d60d28c2c41bd6d46342c238e1a256d79528c651e0ae2df1a1419d97ddb1c5e7c18b88258468c2f2eda37a1cdb42d363b25c
data/Gemfile CHANGED
@@ -5,4 +5,5 @@ gemspec
5
5
  gem 'rake'
6
6
  gem 'pry'
7
7
 
8
+ gem 'bson'
8
9
  gem 'bson_ext', '~>1.7'
data/lib/sanford/cli.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  require 'sanford'
2
- require 'sanford/host_data'
3
- require 'sanford/server'
4
2
  require 'sanford/version'
5
3
 
6
4
  module Sanford
@@ -26,6 +24,7 @@ module Sanford
26
24
  @command = @cli.args.first || 'run'
27
25
  Sanford.config.services_file = @cli.opts['config'] if @cli.opts['config']
28
26
  Sanford.init
27
+ require 'sanford/manager'
29
28
  Sanford::Manager.call(@command, @cli.opts)
30
29
  rescue CLIRB::HelpExit
31
30
  puts help
@@ -52,251 +51,6 @@ module Sanford
52
51
 
53
52
  end
54
53
 
55
- module Manager
56
-
57
- def self.call(action, options = nil)
58
- get_handler_class(action).new(options).tap{ |manager| manager.send(action) }
59
- end
60
-
61
- def self.get_handler_class(action)
62
- case action.to_sym
63
- when :start, :run
64
- ServerHandler
65
- when :stop, :restart
66
- SignalHandler
67
- end
68
- end
69
-
70
- class ServerHandler
71
-
72
- def initialize(options = nil)
73
- @config = Config.new(options)
74
- raise Sanford::NoHostError.new(@config.host_name) if !@config.found_host?
75
- raise Sanford::InvalidHostError.new(@config.host) if !@config.has_listen_args?
76
- @host = @config.host
77
- @logger = @host.logger
78
-
79
- @server_options = {}
80
- # FUTURE allow passing through dat-tcp options (min/max workers)
81
- # FUTURE merge in host options for verbose / keep_alive
82
-
83
- @restart_cmd = RestartCmd.new(@config)
84
- end
85
-
86
- def run
87
- self.run! false
88
- end
89
-
90
- def start
91
- self.run! true
92
- end
93
-
94
- protected
95
-
96
- def run!(daemonize = false)
97
- daemonize!(true) if daemonize && !ENV['SANFORD_SKIP_DAEMONIZE']
98
- Sanford::Server.new(@host, @server_options).tap do |server|
99
- log "Starting #{@host.name} server..."
100
-
101
- server.listen(*@config.listen_args)
102
- $0 = ProcessName.new(@host.name, server.ip, server.port)
103
- log "Listening on #{server.ip}:#{server.port}"
104
-
105
- @config.pid_file.write
106
- log "PID: #{Process.pid}"
107
-
108
- Signal.trap("TERM"){ self.stop!(server) }
109
- Signal.trap("INT"){ self.halt!(server) }
110
- Signal.trap("USR2"){ self.restart!(server) }
111
-
112
- server_thread = server.run(@config.client_file_descriptors)
113
- log "#{@host.name} server started and ready."
114
- server_thread.join
115
- end
116
- rescue RuntimeError => err
117
- log "Error: #{err.message}"
118
- log "#{@host.name} server never started."
119
- ensure
120
- @config.pid_file.remove
121
- end
122
-
123
- def restart!(server)
124
- log "Restarting #{@host.name} server..."
125
- server.pause
126
- log "server paused"
127
-
128
- ENV['SANFORD_HOST'] = @host.name
129
- ENV['SANFORD_SERVER_FD'] = server.file_descriptor.to_s
130
- ENV['SANFORD_CLIENT_FDS'] = server.client_file_descriptors.join(',')
131
- ENV['SANFORD_SKIP_DAEMONIZE'] = 'yes'
132
-
133
- log "calling exec ..."
134
- Dir.chdir @restart_cmd.dir
135
- Kernel.exec(*@restart_cmd.argv)
136
- end
137
-
138
- def stop!(server)
139
- log "Stopping #{@host.name} server..."
140
- server.stop
141
- log "#{@host.name} server stopped."
142
- end
143
-
144
- def halt!(server)
145
- log "Halting #{@host.name} server..."
146
- server.halt false
147
- log "#{@host.name} server halted."
148
- end
149
-
150
- # Full explanation: http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
151
- def daemonize!(no_chdir = false, no_close = false)
152
- exit if fork
153
- Process.setsid
154
- exit if fork
155
- Dir.chdir "/" unless no_chdir
156
- if !no_close
157
- null = File.open "/dev/null", 'w'
158
- STDIN.reopen null
159
- STDOUT.reopen null
160
- STDERR.reopen null
161
- end
162
- return 0
163
- end
164
-
165
- def log(message)
166
- @logger.info "[Sanford] #{message}"
167
- end
168
-
169
- end
170
-
171
- class SignalHandler
172
-
173
- def initialize(options = nil)
174
- @config = Config.new(options)
175
- raise Sanford::NoPIDError.new if !@config.pid
176
- end
177
-
178
- def stop
179
- Process.kill("TERM", @config.pid)
180
- end
181
-
182
- def restart
183
- Process.kill("USR2", @config.pid)
184
- end
185
-
186
- end
187
-
188
- class Config
189
- attr_reader :host_name, :host, :ip, :port, :file_descriptor
190
- attr_reader :client_file_descriptors, :pid_file, :pid, :restart_dir
191
-
192
- def initialize(opts = nil)
193
- options = OpenStruct.new(opts || {})
194
- @host_name = ENV['SANFORD_HOST'] || options.host
195
-
196
- @host = @host_name ? Sanford.hosts.find(@host_name) : Sanford.hosts.first
197
- @host ||= NullHost.new
198
-
199
- @file_descriptor = ENV['SANFORD_SERVER_FD'] || options.file_descriptor
200
- @file_descriptor = @file_descriptor.to_i if @file_descriptor
201
- @ip = ENV['SANFORD_IP'] || options.ip || @host.ip
202
- @port = ENV['SANFORD_PORT'] || options.port || @host.port
203
- @port = @port.to_i if @port
204
-
205
- client_fds_str = ENV['SANFORD_CLIENT_FDS'] || options.client_fds || ""
206
- @client_file_descriptors = client_fds_str.split(',').map(&:to_i)
207
-
208
- @pid_file = PIDFile.new(ENV['SANFORD_PID_FILE'] || options.pid_file || @host.pid_file)
209
- @pid = options.pid || @pid_file.pid
210
-
211
- @restart_dir = ENV['SANFORD_RESTART_DIR'] || options.restart_dir
212
- end
213
-
214
- def listen_args
215
- @file_descriptor ? [ @file_descriptor ] : [ @ip, @port ]
216
- end
217
-
218
- def has_listen_args?
219
- !!@file_descriptor || !!(@ip && @port)
220
- end
221
-
222
- def found_host?
223
- !@host.kind_of?(NullHost)
224
- end
225
-
226
- end
227
-
228
- class NullHost
229
- [ :ip, :port, :pid_file ].each do |method_name|
230
- define_method(method_name){ }
231
- end
232
- end
233
-
234
- class ProcessName < String
235
- def initialize(name, ip, port)
236
- super "#{[ name, ip, port ].join('_')}"
237
- end
238
- end
239
-
240
- class PIDFile
241
- DEF_FILE = '/dev/null'
242
-
243
- def initialize(path)
244
- @path = (path || DEF_FILE).to_s
245
- end
246
-
247
- def pid
248
- pid = File.read(@path).strip if File.exists?(@path)
249
- pid.to_i if pid && !pid.empty?
250
- end
251
-
252
- def write
253
- begin
254
- File.open(@path, 'w'){|f| f.puts Process.pid }
255
- rescue Errno::ENOENT => err
256
- e = RuntimeError.new("Can't write pid to file `#{@path}`")
257
- e.set_backtrace(err.backtrace)
258
- raise e
259
- end
260
- end
261
-
262
- def remove
263
- FileUtils.rm_f(@path)
264
- end
265
-
266
- def to_s
267
- @path
268
- end
269
- end
270
-
271
- class RestartCmd
272
- attr_reader :argv, :dir
273
-
274
- def initialize(config = nil)
275
- require 'rubygems'
276
- config ||= OpenStruct.new
277
- @dir = config.restart_dir || get_pwd
278
- @argv = [ Gem.ruby, $0, ARGV.dup ].flatten
279
- end
280
-
281
- protected
282
-
283
- # Trick from puma/unicorn. Favor PWD because it contains an unresolved
284
- # symlink. This is useful when restarting after deploying; the original
285
- # directory may be removed, but the symlink is pointing to a new
286
- # directory.
287
- def get_pwd
288
- env_stat = File.stat(ENV['PWD'])
289
- pwd_stat = File.stat(Dir.pwd)
290
- if env_stat.ino == pwd_stat.ino && env_stat.dev == pwd_stat.dev
291
- ENV['PWD']
292
- else
293
- Dir.pwd
294
- end
295
- end
296
-
297
- end
298
- end
299
-
300
54
  class CLIRB # Version 1.0.0, https://github.com/redding/cli.rb
301
55
  Error = Class.new(RuntimeError);
302
56
  HelpExit = Class.new(RuntimeError); VersionExit = Class.new(RuntimeError)
@@ -352,27 +106,4 @@ module Sanford
352
106
  end
353
107
  end
354
108
 
355
- class NoHostError < CLIRB::Error
356
- def initialize(host_name)
357
- message = if Sanford.hosts.empty?
358
- "No hosts have been defined. Please define a host before trying to run Sanford."
359
- else
360
- "A host couldn't be found with the name #{host_name.inspect}. "
361
- end
362
- super message
363
- end
364
- end
365
-
366
- class InvalidHostError < CLIRB::Error
367
- def initialize(host)
368
- super "A port must be configured or provided to run a server for '#{host}'"
369
- end
370
- end
371
-
372
- class NoPIDError < CLIRB::Error
373
- def initialize
374
- super "A PID or PID file is required"
375
- end
376
- end
377
-
378
109
  end
data/lib/sanford/host.rb CHANGED
@@ -18,16 +18,16 @@ module Sanford
18
18
  # effects (messing up someone's `initialize`). Thus, the `Configuration`
19
19
  # is a separate class and not on the `Host` directly.
20
20
 
21
- option :name, String
22
- option :ip, String, :default => '0.0.0.0'
23
- option :port, Integer
24
- option :pid_file, Pathname
25
- option :logger, :default => proc{ Sanford.config.logger }
26
- option :verbose_logging, :default => true
27
- option :receives_keep_alive, :default => false
28
- option :runner, :default => proc{ Sanford.config.runner }
29
- option :error_procs, Array, :default => []
30
- option :init_proc, Proc, :default => proc{ }
21
+ option :name, String
22
+ option :ip, String, :default => '0.0.0.0'
23
+ option :port, Integer
24
+ option :pid_file, Pathname
25
+ option :logger, :default => proc{ Sanford.config.logger }
26
+ option :verbose_logging, :default => true
27
+ option :receives_keep_alive, :default => false
28
+ option :runner, :default => proc{ Sanford.config.runner }
29
+ option :error_procs, Array, :default => []
30
+ option :init_procs, Array, :default => []
31
31
 
32
32
  def initialize(host)
33
33
  self.name = host.class.to_s
@@ -88,7 +88,7 @@ module Sanford
88
88
  end
89
89
 
90
90
  def init(&block)
91
- self.configuration.init_proc = block
91
+ self.configuration.init_procs << block
92
92
  end
93
93
 
94
94
  def service_handler_ns(value = nil)
@@ -100,7 +100,7 @@ module Sanford
100
100
  if @service_handler_ns && !(handler_class_name =~ /^::/)
101
101
  handler_class_name = "#{@service_handler_ns}::#{handler_class_name}"
102
102
  end
103
- @services[service_name] = handler_class_name
103
+ @services[service_name.to_s] = handler_class_name
104
104
  end
105
105
 
106
106
  def inspect
@@ -109,8 +109,6 @@ module Sanford
109
109
  "port=#{self.configuration.port.inspect}>"
110
110
  end
111
111
 
112
- protected
113
-
114
112
  module ClassMethods
115
113
 
116
114
  # the class level of a `Host` should just proxy it's methods down to it's
@@ -15,7 +15,7 @@ module Sanford
15
15
  attr_reader :name, :logger, :verbose, :keep_alive, :runner, :error_procs
16
16
 
17
17
  def initialize(service_host, options = nil)
18
- service_host.configuration.init_proc.call
18
+ service_host.configuration.init_procs.each(&:call)
19
19
 
20
20
  overrides = self.remove_nil_values(options || {})
21
21
  configuration = service_host.configuration.to_hash.merge(overrides)
@@ -32,14 +32,14 @@ module Sanford
32
32
  end
33
33
  end
34
34
 
35
- def run(handler_class, request)
36
- self.runner.new(handler_class, request, self.logger).run
37
- end
38
-
39
35
  def handler_class_for(service)
40
36
  @handlers[service] || raise(Sanford::NotFoundError)
41
37
  end
42
38
 
39
+ def run(handler_class, request)
40
+ self.runner.new(handler_class, request, self.logger).run
41
+ end
42
+
43
43
  protected
44
44
 
45
45
  def constantize(handler_class_name)
@@ -57,8 +57,8 @@ module Sanford
57
57
 
58
58
  class NoHandlerClassError < RuntimeError
59
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."
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
62
  end
63
63
  end
64
64
 
@@ -0,0 +1,275 @@
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