red_unicorn 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,2 @@
1
+ == v1.0.0
2
+ * Initial release
data/README.rdoc ADDED
@@ -0,0 +1,43 @@
1
+ == RedUnicorn
2
+
3
+ RedUnicorn provides easy interaction with unicorn processes. It uses signals
4
+ to communicate with unicorn to check its status, stop the processes or to
5
+ provide zero downtime restarts.
6
+
7
+ === Usage
8
+
9
+ $ red_unicorn --help
10
+ RedUnicorn::Unicorn: Unicorn process interactions
11
+ Usage: red_unicorn [opts] (prolicide | status | slaughter | start | restart | reload | stop | birth)
12
+ -h, --help Show this help screen
13
+ -p, --pid /path/to/file.pid Specify path to PID file (default: /var/run/unicorn/unicorn.pid)
14
+ -x, --unicorn-exec /path/to/unicorn Specify path to unicorn executable (default: /var/www/shared/bundle/bin/unicorn_rails)
15
+ -c, --unicorn-config /path/to/config Specify path to unicorn configuration file (default: /etc/unicorn/app.rb)
16
+ -t, --timeout 30 Specify timeout for running actions
17
+ -e, --env production Specify environment (default: production)
18
+ Commands:
19
+ prolicide: Kill single worker process
20
+ status: Returns current unicorn status
21
+ slaughter: Kill all worker processes (master remains)
22
+ start: Start unicorn
23
+ restart: Gracefully restart unicorn (zero downtime)
24
+ reload: Reload unicorn configuration
25
+ stop: Stop unicorn
26
+ birth: Create new worker process
27
+
28
+ === Zero downtime restarts
29
+
30
+ RedUnicorn does its best to provide true zero downtime restarts. After sending
31
+ unicorn the restart signal, it will wait until the new unicorn process has forked
32
+ out workers before killing the original unicorn process. If no worker processes
33
+ are started, the old unicorn process will be left running.
34
+
35
+ == Bugs and Feature requests
36
+
37
+ * Fork, make updates and send pull request
38
+ * Report bugs through github's {issues}[http://github.com/chrisroberts/red_unicorn/issues]
39
+
40
+ == License
41
+
42
+ Copyright (c) 2011 Chris Roberts <chrisroberts.code@gmail.com>
43
+ Licensed under the MIT license (see LICENSE)
data/bin/red_unicorn ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'getoptlong'
5
+
6
+ require 'red_unicorn'
7
+ require 'red_unicorn/unicorn'
8
+
9
+ opts = GetoptLong.new(
10
+ ['--pid-file', '-p', GetoptLong::REQUIRED_ARGUMENT],
11
+ ['--unicorn-exec', '-x', GetoptLong::REQUIRED_ARGUMENT],
12
+ ['--unicorn-config', '-c', GetoptLong::REQUIRED_ARGUMENT],
13
+ ['--timeout', '-t', GetoptLong::REQUIRED_ARGUMENT],
14
+ ['--help', '-h', GetoptLong::NO_ARGUMENT],
15
+ ['--env', '-e', GetoptLong::REQUIRED_ARGUMENT]
16
+ )
17
+
18
+ ALLOWED_ACTIONS = {
19
+ :start => 'Start unicorn',
20
+ :stop => 'Stop unicorn',
21
+ :restart => 'Gracefully restart unicorn (zero downtime)',
22
+ :reload => 'Reload unicorn configuration',
23
+ :birth => 'Create new worker process',
24
+ :prolicide => 'Kill single worker process',
25
+ :slaughter => 'Kill all worker processes (master remains)',
26
+ :status => 'Returns current unicorn status'
27
+ }
28
+
29
+ RETURN_CODES = {
30
+ :success => 0,
31
+ :invalid_action => 1,
32
+ :action_failed => 2,
33
+ :not_running => 3
34
+ }
35
+
36
+ def try_block
37
+ begin
38
+ yield
39
+ [RETURN_CODES[:success], nil]
40
+ rescue => e
41
+ [RETURN_CODES[:action_failed], e]
42
+ end
43
+ end
44
+
45
+ def print_help
46
+ puts 'RedUnicorn::Unicorn: Unicorn process interactions'
47
+ puts "Usage: red_unicorn [opts] (#{ALLOWED_ACTIONS.keys.join(' | ')})"
48
+ puts ' -h, --help Show this help screen'
49
+ puts ' -p, --pid /path/to/file.pid Specify path to PID file (default: /var/run/unicorn/unicorn.pid)'
50
+ puts ' -x, --unicorn-exec /path/to/unicorn Specify path to unicorn executable (default: /var/www/shared/bundle/bin/unicorn_rails)'
51
+ puts ' -c, --unicorn-config /path/to/config Specify path to unicorn configuration file (default: /etc/unicorn/app.rb)'
52
+ puts ' -t, --timeout 30 Specify timeout for running actions'
53
+ puts ' -e, --env production Specify environment (default: production)'
54
+ puts 'Commands:'
55
+ max_width = ALLOWED_ACTIONS.keys.map(&:to_s).map(&:length).max + 8
56
+ ALLOWED_ACTIONS.each_pair do |action, message|
57
+ puts " #{action}:#{' ' * (max_width - action.to_s.length)}#{message}"
58
+ end
59
+ end
60
+
61
+ unicorn_hash = {}
62
+ opts.each do |opt,arg|
63
+ case opt
64
+ when '--timeout'
65
+ unicorn_hash[:action_timeout] = arg.to_i
66
+ when '--unicorn-config'
67
+ unicorn_hash[:unicorn_config] = arg.to_s
68
+ when '--unicorn-exec'
69
+ unicorn_hash[:exec_path] = arg.to_s
70
+ when '--pid'
71
+ unicorn_hash[:pid] = arg.to_s
72
+ when '--env'
73
+ unicorn_hash[:env] = arg.to_s
74
+ when '--help'
75
+ print_help
76
+ exit RETURN_CODES[:success]
77
+ end
78
+ end
79
+
80
+ unicorn = RedUnicorn::Unicorn.new(unicorn_hash)
81
+
82
+ action = ARGV.first
83
+ result = nil
84
+ error = nil
85
+
86
+ case action
87
+ when 'start'
88
+ result,error = try_block{ unicorn.start }
89
+ when 'stop'
90
+ result,error = try_block{ unicorn.stop }
91
+ when 'restart'
92
+ result,error = try_block{ unicorn.restart }
93
+ when 'reload'
94
+ result,error = try_block{ unicorn.reload }
95
+ when 'birth'
96
+ result,error = try_block{ unicorn.add_child }
97
+ when 'prolicide'
98
+ result,error = try_block{ unicorn.remove_child }
99
+ when 'slaughter'
100
+ result,error = try_block{ unicorn.remove_all_children }
101
+ when 'status'
102
+ result,error = try_block{ unicorn.status }
103
+ result = RETURN_CODES[:not_running] if error
104
+ else
105
+ puts "ERROR: Invalid action received: #{action}"
106
+ exit RETURN_CODES[:invalid_action]
107
+ end
108
+
109
+ if(result == RETURN_CODES[:success])
110
+ puts "Unicorn action #{action} completed successfully."
111
+ else
112
+ puts "ERROR: Unicorn action #{action} failed. Please check error logs.\n#{error.class.name}: #{error}\n#{error.backtrace.join("\n")}"
113
+ end
114
+
115
+ exit result
@@ -0,0 +1,180 @@
1
+ #TODO: Add method of checking for rouge unicorn process and killing them
2
+ module RedUnicorn
3
+
4
+ # Descriptive error classes
5
+ class UnicornError < StandardError
6
+ end
7
+ class ActionFailed < UnicornError
8
+ end
9
+ class FileNotFound < UnicornError
10
+ end
11
+ class NotRunning < UnicornError
12
+ end
13
+ class IsRunning < UnicornError
14
+ end
15
+
16
+ class Unicorn
17
+ # pid:: Path to unicorn PID file
18
+ # return_codes:: Hash of return codes (defined in executable bin file)
19
+ # exec_path:: Path to unicorn executable
20
+ # Create a new instance of unicorn interactor
21
+ def initialize(opts={})
22
+ @opts = {
23
+ :pid => '/var/run/unicorn/unicorn.pid',
24
+ :exec_path => '/var/www/shared/bundle/bin/unicorn_rails',
25
+ :config_path => '/etc/unicorn/app.rb',
26
+ :action_timeout => 30,
27
+ :env => 'production'
28
+ }.merge(opts)
29
+ check_exec_path
30
+ end
31
+
32
+ # Start a new unicorn process
33
+ def start
34
+ process_is :stopped do
35
+ %x{#{@opts[:exec_path]} --daemonize --env #{@opts[:env]} --config-file #{@opts[:config_path]}}
36
+ end
37
+ end
38
+
39
+ # Stop current unicorn process
40
+ def stop
41
+ process_is :running do
42
+ Process.kill('QUIT', pid)
43
+ end
44
+ end
45
+
46
+ # Halts the current unicorn process
47
+ def halt
48
+ process_is :running do
49
+ Process.kill('TERM', pid)
50
+ end
51
+ end
52
+
53
+ # Graceful restart
54
+ def restart
55
+ process_is :running do
56
+ original_pid = pid
57
+ Process.kill('USR2', pid)
58
+ sleep(0.1) # give unicorn some breathing room
59
+ waited = 0
60
+ until((File.exists?(@opts[:pid]) && is_running? && !child_pids(pid).empty?) || waited > @opts[:action_timeout])
61
+ sleep_start = Time.now.to_f
62
+ sleep(0.2)
63
+ waited += Time.now.to_f - sleep_start
64
+ end
65
+ if(pid == original_pid || waited > @opts[:action_timeout])
66
+ raise UnicornError.new 'Failed to start new process'
67
+ end
68
+ Process.kill('QUIT', original_pid)
69
+ while(is_running?(original_pid) && waited < @opts[:action_timeout])
70
+ sleep_start = Time.now.to_f
71
+ sleep(0.2)
72
+ waited += Time.now.to_f - sleep_start
73
+ end
74
+ raise IsRunning.new 'Failed to stop original unicorn process' if is_running?(original_pid)
75
+ raise NotRunning.new 'Failed to start new unicorn process' unless is_running?
76
+ reopen_logs
77
+ end
78
+ end
79
+
80
+ # Reload unicorn configuration
81
+ def reload
82
+ process_is :running do
83
+ Process.kill('HUP', pid)
84
+ end
85
+ end
86
+
87
+ # Add new worker process
88
+ def add_child
89
+ process_is :running do
90
+ Process.kill('TTIN', pid)
91
+ end
92
+ end
93
+
94
+ # Remove worker process
95
+ def remove_child
96
+ process_is :running do
97
+ Process.kill('TTOU', pid)
98
+ end
99
+ end
100
+
101
+ # Stops all worker processes but
102
+ # keeps the master process alive
103
+ def remove_all_children
104
+ process_is :running do
105
+ Process.kill('WINCH', pid)
106
+ end
107
+ end
108
+
109
+ # Reopen log files
110
+ def reopen_logs
111
+ process_is :running do
112
+ Process.kill('USR1', pid)
113
+ end
114
+ end
115
+
116
+ # Return status of current process
117
+ def status
118
+ process_is :running do
119
+ puts '* unicorn is running'
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ # Return process ID from PID file
126
+ def pid
127
+ if(File.exists?(@opts[:pid]))
128
+ File.read(@opts[:pid]).to_s.strip.to_i
129
+ else
130
+ raise FileNotFound.new "PID file not found. Provided path: #{@opts[:pid]}"
131
+ end
132
+ end
133
+
134
+ # Check if unicorn is currently running
135
+ def is_running?(custom_pid = nil)
136
+ begin
137
+ Process.kill(0, custom_pid || pid)
138
+ true
139
+ rescue
140
+ false
141
+ end
142
+ end
143
+
144
+ # state:: :running or :stopped
145
+ # Ensure process is in given state and run some code if
146
+ # the caller felt like passing some along
147
+ def process_is(state)
148
+ case state
149
+ when :running
150
+ raise NotRunning.new 'Unicorn is not currently running' unless is_running?
151
+ when :stopped
152
+ raise IsRunning.new 'Unicorn is currently running' if is_running?
153
+ else
154
+ raise UnicornError.new 'Unknown process state received'
155
+ end
156
+ yield if block_given?
157
+ end
158
+
159
+ # parent_pid:: Parent process ID
160
+ # Returns array of child process IDs for the given parent
161
+ def child_pids(parent_pid)
162
+ process_list = %x{ps -eo pid,ppid | grep #{parent_pid}}
163
+ process_list.map(&:strip).find_all{|pr| pr.split.last == parent_pid.to_s }.map{|pr| pr.split.first.strip.to_i }
164
+ end
165
+
166
+ # Check validity of unicorn exec path
167
+ def check_exec_path
168
+ unless(File.exists?(@opts[:exec_path]))
169
+ test_path = '/var/www/shared/bundle/bin/unicorn_rails'
170
+ if(File.exists?(test_path))
171
+ @opts[:exec_path] = test_path
172
+ else
173
+ raise FileNotFound.new "Failed to find executable unicorn. Set path is: #{@opts[:exec_path]}"
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+
@@ -0,0 +1,17 @@
1
+ module RedUnicorn
2
+ class Version
3
+
4
+ attr_reader :major, :minor, :tiny
5
+
6
+ def initialize(version)
7
+ version = version.split('.')
8
+ @major, @minor, @tiny = [version[0].to_i, version[1].to_i, version[2].to_i]
9
+ end
10
+
11
+ def to_s
12
+ "#{@major}.#{@minor}.#{@tiny}"
13
+ end
14
+ end
15
+
16
+ VERSION = Version.new('1.0.0')
17
+ end
@@ -0,0 +1 @@
1
+ require 'red_unicorn/version'
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: red_unicorn
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Chris Roberts
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-08 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: unicorn
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Unicorn Process Handler
36
+ email: chrisroberts.code@gmail.com
37
+ executables:
38
+ - red_unicorn
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ - CHANGELOG.rdoc
44
+ files:
45
+ - README.rdoc
46
+ - CHANGELOG.rdoc
47
+ - bin/red_unicorn
48
+ - lib/red_unicorn.rb
49
+ - lib/red_unicorn/unicorn.rb
50
+ - lib/red_unicorn/version.rb
51
+ has_rdoc: true
52
+ homepage: http://github.com/chrisroberts/red_unicorn
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 3
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.6.2
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Unicorn Process Handler
85
+ test_files: []
86
+