mikehale-daemons 1.0.12.1

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.
@@ -0,0 +1,226 @@
1
+
2
+ module Daemons
3
+ class ApplicationGroup
4
+
5
+ attr_reader :app_name
6
+ attr_reader :script
7
+
8
+ attr_reader :monitor
9
+
10
+ #attr_reader :controller
11
+
12
+ attr_reader :options
13
+
14
+ attr_reader :applications
15
+
16
+ attr_accessor :controller_argv
17
+ attr_accessor :app_argv
18
+
19
+ attr_accessor :dir_mode
20
+ attr_accessor :dir
21
+
22
+ # true if the application is supposed to run in multiple instances
23
+ attr_reader :multiple
24
+
25
+
26
+ def initialize(app_name, options = {})
27
+ @app_name = app_name
28
+ @options = options
29
+
30
+ if options[:script]
31
+ @script = File.expand_path(options[:script])
32
+ end
33
+
34
+ #@controller = controller
35
+ @monitor = nil
36
+
37
+ #options = controller.options
38
+
39
+ @multiple = options[:multiple] || false
40
+
41
+ @dir_mode = options[:dir_mode] || :script
42
+ @dir = options[:dir] || ''
43
+
44
+ @keep_pid_files = options[:keep_pid_files] || false
45
+
46
+ #@applications = find_applications(pidfile_dir())
47
+ @applications = []
48
+ end
49
+
50
+ # Setup the application group.
51
+ # Currently this functions calls <tt>find_applications</tt> which finds
52
+ # all running instances of the application and populates the application array.
53
+ #
54
+ def setup
55
+ @applications = find_applications(pidfile_dir())
56
+ end
57
+
58
+ def pidfile_dir
59
+ PidFile.dir(@dir_mode, @dir, script)
60
+ end
61
+
62
+ def find_applications(dir)
63
+ pid_files = PidFile.find_files(dir, app_name, ! @keep_pid_files)
64
+
65
+ #pp pid_files
66
+
67
+ @monitor = Monitor.find(dir, app_name + '_monitor')
68
+
69
+ pid_files.reject! {|f| f =~ /_monitor.pid$/}
70
+
71
+ return pid_files.map {|f|
72
+ app = Application.new(self, {}, PidFile.existing(f))
73
+ setup_app(app)
74
+ app
75
+ }
76
+ end
77
+
78
+ def new_application(add_options = {})
79
+ if @applications.size > 0 and not @multiple
80
+ if options[:force]
81
+ @applications.delete_if {|a|
82
+ unless a.running?
83
+ a.zap
84
+ true
85
+ end
86
+ }
87
+ end
88
+
89
+ raise RuntimeException.new('there is already one or more instance(s) of the program running') unless @applications.empty?
90
+ end
91
+
92
+ app = Application.new(self, add_options)
93
+
94
+ setup_app(app)
95
+
96
+ @applications << app
97
+
98
+ return app
99
+ end
100
+
101
+ def setup_app(app)
102
+ app.controller_argv = @controller_argv
103
+ app.app_argv = @app_argv
104
+ end
105
+ private :setup_app
106
+
107
+ def create_monitor(an_app)
108
+ return if @monitor
109
+
110
+ if options[:monitor]
111
+ @monitor = Monitor.new(an_app)
112
+
113
+ @monitor.start(@applications)
114
+ end
115
+ end
116
+
117
+ def start_all
118
+ @monitor.stop if @monitor
119
+ @monitor = nil
120
+
121
+ @applications.each {|a|
122
+ fork {
123
+ a.start
124
+ }
125
+ }
126
+ end
127
+
128
+ # Specify :force_kill_wait => (seconds to wait) and this method will
129
+ # block until the process is dead. It first sends a TERM signal, then
130
+ # a KILL signal (-9) if the process hasn't died after the wait time.
131
+ # Note: The force argument is from the original daemons implementation.
132
+ def stop_all(force = false)
133
+ @monitor.stop if @monitor
134
+
135
+ failed_to_kill = false
136
+ debug = options[:debug]
137
+ wait = options[:force_kill_wait].to_i
138
+ pids = unix_pids
139
+ if wait > 0 && pids.size > 0
140
+ puts "[daemons_ext]: Killing #{app_name} with force after #{wait} secs."
141
+ STDOUT.flush
142
+
143
+ # Send term first, don't delete PID files.
144
+ pids.each {|pid| Process.kill('TERM', pid) rescue Errno::ESRCH}
145
+
146
+ begin
147
+ Timeout::timeout(wait) {block_on_pids(wait, debug, options[:sleepy_time] || 1)}
148
+ rescue Timeout::Error
149
+ puts "[daemons_ext]: Time is up! Forcefully killing #{unix_pids.size} #{app_name}(s)..."
150
+ STDOUT.flush
151
+ unix_pids.each {|pid| `kill -9 #{pid}`}
152
+ begin
153
+ # Give it an extra 30 seconds to kill -9
154
+ Timeout::timeout(30) {block_on_pids(wait, debug, options[:sleepy_time] || 1)}
155
+ rescue Timeout::Error
156
+ failed_to_kill = true
157
+ puts "[daemons_ext]: #{unix_pids} #{app_name}(s) won't die! Giving up."
158
+ STDOUT.flush
159
+ end
160
+ ensure
161
+ # Delete Pidfiles
162
+ @applications.each {|a| a.zap!}
163
+ end
164
+
165
+ puts "[daemons_ext]: All #{app_name}s dead." unless failed_to_kill
166
+ STDOUT.flush
167
+ else
168
+ @applications.each {|a|
169
+ if force
170
+ begin; a.stop; rescue ::Exception; end
171
+ else
172
+ a.stop
173
+ end
174
+ }
175
+ end
176
+ end
177
+
178
+ def zap_all
179
+ @monitor.stop if @monitor
180
+
181
+ @applications.each {|a| a.zap}
182
+ end
183
+
184
+ def show_status
185
+ @applications.each {|a| a.show_status}
186
+ end
187
+
188
+ private
189
+
190
+ # Block until all unix_pids are gone (should be wrapped in a timeout)
191
+ def block_on_pids(wait, debug, sleepy_time = 1)
192
+ started_at = Time.now
193
+ num_pids = unix_pids.size
194
+ while num_pids > 0
195
+ time_left = wait - (Time.now - started_at)
196
+ puts "[daemons_ext]: Waiting #{time_left.round} secs on " +
197
+ "#{num_pids} #{app_name}(s)..."
198
+ unix_pids.each {|pid| puts "\t#{pid}"} if debug
199
+ STDOUT.flush
200
+ sleep sleepy_time
201
+ num_pids = unix_pids.size
202
+ end
203
+ end
204
+
205
+ # Find UNIX pids based on app_name. CAUTION: This has only been tested on
206
+ # Mac OS X and CentOS.
207
+ def unix_pids
208
+ pids = []
209
+ x = `ps auxw | grep -v grep | awk '{print $2, $11}' | grep #{app_name}`
210
+ if x && x.chomp!
211
+ processes = x.split(/\n/).compact
212
+ processes = processes.delete_if do |p|
213
+ pid, name = p.split(/\s/)
214
+ # We want to make sure that the first part of the process name matches
215
+ # so that app_name matches app_name_22
216
+ app_name != name[0..(app_name.length - 1)]
217
+ end
218
+ pids = processes.map {|p| p.split(/\s/)[0].to_i}
219
+ end
220
+
221
+ pids
222
+ end
223
+
224
+ end
225
+
226
+ end
@@ -0,0 +1,19 @@
1
+ require 'daemons/etc_extension'
2
+
3
+ class CurrentProcess
4
+ def self.change_privilege(user, group=user)
5
+ puts "Changing process privilege to #{user}:#{group}"
6
+
7
+ uid, gid = Process.euid, Process.egid
8
+ target_uid = Etc.getpwnam(user).uid
9
+ target_gid = Etc.getgrnam(group).gid
10
+
11
+ if uid != target_uid || gid != target_gid
12
+ Process.initgroups(user, target_gid)
13
+ Process::GID.change_privilege(target_gid)
14
+ Process::UID.change_privilege(target_uid)
15
+ end
16
+ rescue Errno::EPERM => e
17
+ raise "Couldn't change user and group to #{user}:#{group}: #{e}"
18
+ end
19
+ end
@@ -0,0 +1,117 @@
1
+
2
+ module Daemons
3
+
4
+ class Optparse
5
+
6
+ attr_reader :usage
7
+
8
+ def initialize(controller)
9
+ @controller = controller
10
+ @options = {}
11
+
12
+ @opts = OptionParser.new do |opts|
13
+ #opts.banner = "Usage: example.rb [options]"
14
+ opts.banner = ""
15
+
16
+ # Boolean switch.
17
+ # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
18
+ # @options[:verbose] = v
19
+ # end
20
+
21
+ opts.on("-t", "--ontop", "Stay on top (does not daemonize)") do |t|
22
+ @options[:ontop] = t
23
+ end
24
+
25
+ opts.on("-f", "--force", "Force operation") do |t|
26
+ @options[:force] = t
27
+ end
28
+
29
+ #opts.separator ""
30
+ #opts.separator "Specific options:"
31
+
32
+
33
+ opts.separator ""
34
+ opts.separator "Common options:"
35
+
36
+ # No argument, shows at tail. This will print an options summary.
37
+ # Try it and see!
38
+ opts.on_tail("-h", "--help", "Show this message") do
39
+ #puts opts
40
+ #@usage =
41
+ controller.print_usage()
42
+
43
+ exit
44
+ end
45
+
46
+ # Another typical switch to print the version.
47
+ opts.on_tail("--version", "Show version") do
48
+ puts "daemons version #{Daemons::VERSION}"
49
+ exit
50
+ end
51
+ end
52
+
53
+ begin
54
+ @usage = @opts.to_s
55
+ rescue ::Exception # work around a bug in ruby 1.9
56
+ @usage = <<END
57
+ -t, --ontop Stay on top (does not daemonize)
58
+ -f, --force Force operation
59
+
60
+ Common options:
61
+ -h, --help Show this message
62
+ --version Show version
63
+ END
64
+ end
65
+ end
66
+
67
+
68
+ #
69
+ # Return a hash describing the options.
70
+ #
71
+ def parse(args)
72
+ # The options specified on the command line will be collected in *options*.
73
+ # We set default values here.
74
+ #options = {}
75
+
76
+
77
+ ##pp args
78
+ @opts.parse(args)
79
+
80
+ return @options
81
+ end
82
+
83
+ end
84
+
85
+
86
+ class Controller
87
+
88
+ def print_usage
89
+ puts "Usage: #{@app_name} <command> <options> -- <application options>"
90
+ puts
91
+ puts "* where <command> is one of:"
92
+ puts " start start an instance of the application"
93
+ puts " stop stop all instances of the application"
94
+ puts " restart stop all instances and restart them afterwards"
95
+ puts " run start the application and stay on top"
96
+ puts " zap set the application to a stopped state"
97
+ puts
98
+ puts "* and where <options> may contain several of the following:"
99
+
100
+ puts @optparse.usage
101
+ end
102
+
103
+ def catch_exceptions(&block)
104
+ begin
105
+ block.call
106
+ rescue CmdException, OptionParser::ParseError => e
107
+ puts "ERROR: #{e.to_s}"
108
+ puts
109
+ print_usage()
110
+ rescue RuntimeException => e
111
+ puts "ERROR: #{e.to_s}"
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ end
@@ -0,0 +1,134 @@
1
+
2
+ module Daemons
3
+ class Controller
4
+
5
+ attr_reader :app_name
6
+
7
+ attr_reader :group
8
+
9
+ attr_reader :options
10
+
11
+
12
+ COMMANDS = [
13
+ 'start',
14
+ 'stop',
15
+ 'restart',
16
+ 'run',
17
+ 'zap',
18
+ 'status'
19
+ ]
20
+
21
+ def initialize(options = {}, argv = [])
22
+ @options = options
23
+ @argv = argv
24
+
25
+ # Allow an app_name to be specified. If not specified use the
26
+ # basename of the script.
27
+ @app_name = options[:app_name]
28
+
29
+ if options[:script]
30
+ @script = File.expand_path(options[:script])
31
+
32
+ @app_name ||= File.split(@script)[1]
33
+ end
34
+
35
+ @app_name ||= 'unknown_application'
36
+
37
+ @command, @controller_part, @app_part = Controller.split_argv(argv)
38
+
39
+ #@options[:dir_mode] ||= :script
40
+
41
+ @optparse = Optparse.new(self)
42
+ end
43
+
44
+
45
+ # This function is used to do a final update of the options passed to the application
46
+ # before they are really used.
47
+ #
48
+ # Note that this function should only update <tt>@options</tt> and no other variables.
49
+ #
50
+ def setup_options
51
+ #@options[:ontop] ||= true
52
+ end
53
+
54
+ def run
55
+ @options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
56
+
57
+ setup_options()
58
+
59
+ #pp @options
60
+
61
+ @group = ApplicationGroup.new(@app_name, @options)
62
+ @group.controller_argv = @controller_part
63
+ @group.app_argv = @app_part
64
+
65
+ @group.setup
66
+
67
+ case @command
68
+ when 'start'
69
+ @group.new_application.start
70
+ when 'run'
71
+ @options[:ontop] ||= true
72
+ @group.new_application.start
73
+ when 'stop'
74
+ @group.stop_all
75
+ when 'restart'
76
+ unless @group.applications.empty?
77
+ @group.stop_all
78
+ sleep 1
79
+ @group.start_all
80
+ end
81
+ when 'zap'
82
+ @group.zap_all
83
+ when 'status'
84
+ unless @group.applications.empty?
85
+ @group.show_status
86
+ else
87
+ puts "#{@group.app_name}: no instances running"
88
+ end
89
+ when nil
90
+ raise CmdException.new('no command given')
91
+ #puts "ERROR: No command given"; puts
92
+
93
+ #print_usage()
94
+ #raise('usage function not implemented')
95
+ else
96
+ raise Error.new("command '#{@command}' not implemented")
97
+ end
98
+ end
99
+
100
+
101
+ # Split an _argv_ array.
102
+ # +argv+ is assumed to be in the following format:
103
+ # ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
104
+ #
105
+ # <tt>command</tt> must be one of the commands listed in <tt>COMMANDS</tt>
106
+ #
107
+ # *Returns*: the command as a string, the controller options as an array, the appliation options
108
+ # as an array
109
+ #
110
+ def Controller.split_argv(argv)
111
+ argv = argv.dup
112
+
113
+ command = nil
114
+ controller_part = []
115
+ app_part = []
116
+
117
+ if COMMANDS.include? argv[0]
118
+ command = argv.shift
119
+ end
120
+
121
+ if i = argv.index('--')
122
+ # Handle the case where no controller options are given, just
123
+ # options after "--" as well (i == 0)
124
+ controller_part = (i == 0 ? [] : argv[0..i-1])
125
+ app_part = argv[i+1..-1]
126
+ else
127
+ controller_part = argv[0..-1]
128
+ end
129
+
130
+ return command, controller_part, app_part
131
+ end
132
+ end
133
+
134
+ end