red_unicorn 1.0.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.
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
+