processwanker 0.0.7

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,218 @@
1
+ ############################################################################
2
+ #
3
+ # process_service.rb
4
+ #
5
+ # class representing a service that is a command-line launchable process
6
+ #
7
+ ############################################################################
8
+
9
+ require 'service'
10
+ require 'digest/md5'
11
+ require 'etc'
12
+ require 'net_util'
13
+
14
+ module ProcessWanker
15
+
16
+ class ProcessService < Service
17
+
18
+ include Log
19
+
20
+ ############################################################################
21
+ #
22
+ #
23
+ #
24
+ ############################################################################
25
+
26
+ def self.nice_name
27
+ "process_service"
28
+ end
29
+
30
+ ############################################################################
31
+ #
32
+ # iparams is a hash containing:
33
+ #
34
+ # :start_cmd
35
+ # :start_dir (optional)
36
+ # :stop_cmd (optional)
37
+ # :run_user (optional)
38
+ # :soft_kill_limit (optional)
39
+ #
40
+ # plus anything to be passed to Service
41
+ #
42
+ ############################################################################
43
+
44
+ def initialize(iparams)
45
+
46
+ # extract parameters
47
+ extract_params(
48
+ iparams,
49
+ [
50
+ :start_cmd,
51
+ :start_dir,
52
+ :stop_cmd,
53
+ :run_user,
54
+ :soft_kill_limit,
55
+ ])
56
+
57
+ # set defaults
58
+ @params=
59
+ {
60
+ :soft_kill_limit => 3,
61
+ :start_dir => "/"
62
+ }.merge(@params)
63
+
64
+ raise "service has no start_cmd" if(!@params[:start_cmd])
65
+
66
+ # determine run_user properties
67
+ if(@params[:run_user])
68
+ if(@params[:run_user].class == String)
69
+ @params[:run_user]=Etc.getpwnam(@params[:run_user])
70
+ elsif(@params[:run_user].class == Fixnum)
71
+ @params[:run_user]=Etc.getpwuid(@params[:run_user].to_i)
72
+ else
73
+ raise "bad run_user parameter for process_service"
74
+ end
75
+
76
+ # verify we can switch to this uid if necessary
77
+ current_uid=Process.euid()
78
+ if(current_uid != 0 && current_uid != @params[:run_user].uid)
79
+ raise "can't have a :run_user parameter unless we are running as root, or the uids match"
80
+ end
81
+
82
+ end
83
+
84
+ super(iparams)
85
+ end
86
+
87
+ ############################################################################
88
+ #
89
+ # start
90
+ #
91
+ # fork() and exec() the start_cmd
92
+ #
93
+ ############################################################################
94
+
95
+ def do_start(attempt_count)
96
+ info("do_start[#{attempt_count}] for #{self.name}")
97
+
98
+ Process.fork do
99
+
100
+ # close network descriptors
101
+ NetUtil::post_fork()
102
+
103
+ # start new session
104
+ Process.setsid()
105
+
106
+ # set environment cookie so we can be identified
107
+ hash=env_hash()
108
+ ENV[ ProcessUtil::ENVIRONMENT_KEY ] = hash if(hash)
109
+
110
+ # change user/group?
111
+ if(@params[:run_user])
112
+
113
+ current_uid=Process.euid()
114
+ current_user=Etc.getpwuid(current_uid)
115
+
116
+ if(current_uid != @params[:run_user].uid)
117
+ Process.uid=@params[:run_user].uid
118
+ Process.gid=@params[:run_user].gid
119
+ Process.euid=@params[:run_user].uid
120
+ Process.egid=@params[:run_user].gid
121
+ ENV["HOME"]=@params[:run_user].dir
122
+ end
123
+
124
+ end
125
+
126
+ # change directory?
127
+ Dir.chdir(@params[:start_dir])
128
+
129
+ # redirect inputs/outputs
130
+ STDIN.reopen("/dev/null") # - closing STDIN causes problems with apache
131
+ file=@params[:log_file] ? @params[:log_file] : "/dev/null"
132
+ STDOUT.reopen(file,"a")
133
+ STDERR.reopen(file,"a")
134
+
135
+ # run!
136
+ Process.exec(@params[:start_cmd])
137
+
138
+ end
139
+
140
+ end
141
+
142
+ ############################################################################
143
+ #
144
+ # stop
145
+ #
146
+ # stop the process (either with -TERM or -KILL)
147
+ #
148
+ ############################################################################
149
+
150
+ def do_stop(attempt_count)
151
+
152
+ kl=@params[:soft_kill_limit]
153
+ mode = (kl && attempt_count >= kl) ? :hard : :soft
154
+
155
+ info("do_stop[#{attempt_count}]->#{mode} for #{self.name}")
156
+
157
+ if(mode == :soft)
158
+ if(params[:stop_cmd])
159
+ system(params[:stop_cmd])
160
+ return
161
+ end
162
+ end
163
+
164
+ # find all processes with matching hash
165
+ procs=ProcessUtil::processes[ env_hash ]
166
+ if(procs)
167
+ procs.each do |pid|
168
+ Process.kill({ :hard => "KILL", :soft => "TERM" }[mode],pid)
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ ############################################################################
175
+ #
176
+ # ping
177
+ #
178
+ # return run state of process
179
+ #
180
+ ############################################################################
181
+
182
+ def do_ping
183
+ ProcessUtil::processes[env_hash] ? :up : :down
184
+ end
185
+
186
+ ############################################################################
187
+ #
188
+ # env_hash
189
+ #
190
+ # returns magic environment cookie that identifies processes belonging to
191
+ # this service.
192
+ #
193
+ ############################################################################
194
+
195
+ def env_hash()
196
+ Digest::MD5.hexdigest( name )
197
+ end
198
+
199
+ ############################################################################
200
+ #
201
+ #
202
+ #
203
+ ############################################################################
204
+
205
+ ############################################################################
206
+ #
207
+ #
208
+ #
209
+ ############################################################################
210
+
211
+ end
212
+
213
+
214
+ ServiceMgr::register_service_class(ProcessService)
215
+
216
+ end
217
+
218
+
@@ -0,0 +1,103 @@
1
+ ############################################################################
2
+ #
3
+ # upstart_service.rb
4
+ #
5
+ # class representing a service controlled by upstart
6
+ #
7
+ ############################################################################
8
+
9
+ require 'service'
10
+ require 'digest/md5'
11
+ require 'etc'
12
+ require 'process_service'
13
+ require 'process_util'
14
+
15
+ #<BRS> used to require dbus, but upstart doesn't seem to use it in Ubuntu Server
16
+ #require 'rubygems'
17
+ #gem 'ruby-dbus'
18
+ #require 'dbus'
19
+
20
+ module ProcessWanker
21
+
22
+ class UpstartService < Service
23
+
24
+ include Log
25
+
26
+ ############################################################################
27
+ #
28
+ #
29
+ #
30
+ ############################################################################
31
+
32
+ def self.nice_name
33
+ "upstart_service"
34
+ end
35
+
36
+ ############################################################################
37
+ #
38
+ #
39
+ #
40
+ ############################################################################
41
+
42
+ def initialize(iparams)
43
+
44
+ # extract parameters
45
+ extract_params(
46
+ iparams,
47
+ [
48
+ :job_name,
49
+ ])
50
+
51
+ @job_name=@params[:job_name] || iparams[:name]
52
+
53
+ super(iparams)
54
+ end
55
+
56
+ ############################################################################
57
+ #
58
+ # start
59
+ #
60
+ ############################################################################
61
+
62
+ def do_start(attempt_count)
63
+ debug("do_start #{self.name}")
64
+ system("initctl start #{@job_name}")
65
+ end
66
+
67
+ ############################################################################
68
+ #
69
+ # stop
70
+ #
71
+ ############################################################################
72
+
73
+ def do_stop(attempt_count)
74
+ system("initctl stop #{@job_name}")
75
+ end
76
+
77
+ ############################################################################
78
+ #
79
+ # ping
80
+ #
81
+ # return run state of process
82
+ #
83
+ ############################################################################
84
+
85
+ def do_ping
86
+ status=`initctl status #{@job_name}`
87
+ status.include?("running") ? :up : :down
88
+ end
89
+
90
+ ############################################################################
91
+ #
92
+ #
93
+ #
94
+ ############################################################################
95
+
96
+ end
97
+
98
+
99
+ ServiceMgr::register_service_class(UpstartService)
100
+
101
+ end
102
+
103
+
@@ -0,0 +1,226 @@
1
+ ############################################################################
2
+ #
3
+ # service_mgr.rb
4
+ #
5
+ # the core of the application
6
+ #
7
+ ############################################################################
8
+
9
+ require 'process_util'
10
+ require 'config'
11
+ require 'service'
12
+
13
+ module ProcessWanker
14
+ class ServiceMgr
15
+
16
+ include Log
17
+
18
+ ############################################################################
19
+ #
20
+ #
21
+ #
22
+ ############################################################################
23
+
24
+ CLASS_SUBDIR = "service_classes"
25
+ CLASS_DIR = File.join(File.dirname(File.expand_path(__FILE__)),CLASS_SUBDIR)
26
+
27
+ ############################################################################
28
+ #
29
+ #
30
+ #
31
+ ############################################################################
32
+
33
+ attr_accessor :services_by_name
34
+ attr_accessor :tick_count
35
+ attr_accessor :service_classes
36
+ attr_accessor :reload_config
37
+ attr_accessor :terminate
38
+
39
+ ############################################################################
40
+ #
41
+ #
42
+ #
43
+ ############################################################################
44
+
45
+ class << self
46
+ def instance
47
+ @@instance
48
+ end
49
+ def register_service(service)
50
+ @@instance.register_service(service)
51
+ end
52
+ def register_service_class(service_class)
53
+ @@instance.register_service_class(service_class)
54
+ end
55
+ end
56
+
57
+ ############################################################################
58
+ #
59
+ # initialize
60
+ #
61
+ ############################################################################
62
+
63
+ def initialize()
64
+ @@instance=self
65
+ @services_by_name={}
66
+ @service_classes={}
67
+ @tick_count=0
68
+
69
+ ProcessUtil::scan_processes()
70
+ load_classes()
71
+ end
72
+
73
+ ############################################################################
74
+ #
75
+ #
76
+ #
77
+ ############################################################################
78
+
79
+ def load_classes()
80
+ $LOAD_PATH << CLASS_DIR unless($LOAD_PATH.include?(CLASS_DIR))
81
+ Dir.glob( File.join(CLASS_DIR,"*.rb") ).each do |fn|
82
+ require( File.basename(fn,".rb") )
83
+ end
84
+ end
85
+
86
+ ############################################################################
87
+ #
88
+ #
89
+ #
90
+ ############################################################################
91
+
92
+ def register_service(service)
93
+ raise "service is missing name" if(!service.name)
94
+ raise "service #{service.name} is multiply defined" if(@services_by_name[service.name])
95
+ @services_by_name[service.name]=service
96
+ end
97
+
98
+ ############################################################################
99
+ #
100
+ #
101
+ #
102
+ ############################################################################
103
+
104
+ def register_service_class(service_class)
105
+ @service_classes[ service_class.nice_name ]=service_class
106
+ end
107
+
108
+ ############################################################################
109
+ #
110
+ # main tick function
111
+ #
112
+ ############################################################################
113
+
114
+ def tick()
115
+
116
+ # scan processes... and kill anything we don't recognize
117
+ ProcessUtil::scan_processes()
118
+ ProcessUtil::kill_unknown()
119
+ ProcessUtil::reap()
120
+
121
+ # tick each service in turn
122
+ @services_by_name.values.each do |service|
123
+ service.tick()
124
+ end
125
+
126
+ @tick_count += 1
127
+
128
+ end
129
+
130
+ ############################################################################
131
+ #
132
+ #
133
+ #
134
+ ############################################################################
135
+
136
+ def match_services(spec)
137
+ matched={}
138
+ @services_by_name.each do |sn,s|
139
+ matched[sn]=s if(s.matches_spec(spec))
140
+ end
141
+ matched
142
+ end
143
+
144
+ ############################################################################
145
+ #
146
+ #
147
+ #
148
+ ############################################################################
149
+
150
+ def apply_config(cfg,notboot=true)
151
+
152
+ @services_by_name={}
153
+
154
+ cfg.daemon.services.each do |group_name,services|
155
+ services.services.each do |name,service|
156
+ params=service.params.merge({ :name => service.name, :group_name => group_name })
157
+ if(!notboot)
158
+ params[:initial_state]=:up
159
+ end
160
+ svc=service.klass.new(params)
161
+
162
+ # the service will register itself
163
+ # we must store its config block
164
+ svc.config_node=service
165
+
166
+ end
167
+ end
168
+
169
+ @services_by_name.each do |n,v|
170
+ v.resolve_dependencies()
171
+ end
172
+
173
+ ProcessUtil::build_known_hashes()
174
+
175
+ end
176
+
177
+ ############################################################################
178
+ #
179
+ #
180
+ #
181
+ ############################################################################
182
+
183
+ def run()
184
+
185
+ while(true)
186
+
187
+ tick()
188
+ sleep(1)
189
+
190
+ #
191
+ # check for requests
192
+ #
193
+
194
+ if(@reload_config)
195
+ info("reloading configuration")
196
+ ProcessWanker::with_logged_rescue("ServiceMgr - reloading configuration") do
197
+ c=Config::load_config(Config::get_config_path)
198
+ apply_config(c)
199
+ end
200
+ @reload_config=false
201
+ end
202
+
203
+ if(@terminate)
204
+ info("terminating")
205
+ break
206
+ end
207
+
208
+ end
209
+ end
210
+
211
+
212
+ ############################################################################
213
+ #
214
+ #
215
+ #
216
+ ############################################################################
217
+
218
+ ############################################################################
219
+ #
220
+ #
221
+ #
222
+ ############################################################################
223
+
224
+ end
225
+ end
226
+
data/lib/util.rb ADDED
@@ -0,0 +1,31 @@
1
+ ############################################################################
2
+ #
3
+ # util.rb
4
+ #
5
+ ############################################################################
6
+
7
+ require 'log'
8
+
9
+ module ProcessWanker
10
+
11
+ ############################################################################
12
+ #
13
+ #
14
+ #
15
+ ############################################################################
16
+
17
+ def with_logged_rescue(context=nil,level=Log::ERROR)
18
+
19
+ begin
20
+ yield
21
+ rescue Exception => e
22
+ Log::log(context,level) if(context)
23
+ Log::log(e.message,level)
24
+ Log::log(e.backtrace.join("\n"),level)
25
+ end
26
+
27
+ end
28
+ module_function :with_logged_rescue
29
+
30
+
31
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{processwanker}
5
+ s.version = "0.0.7"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ben Stragnell"]
9
+ s.cert_chain = ["/Users/ben/gem_certs/gem-public_cert.pem"]
10
+ s.date = %q{2011-05-05}
11
+ s.default_executable = %q{pw}
12
+ s.description = %q{Process monitoring and remote control system}
13
+ s.email = %q{pw@codepuppies.com}
14
+ s.executables = ["pw"]
15
+ s.extra_rdoc_files = ["README", "bin/pw", "lib/config/config.rb", "lib/config/config_auth.rb", "lib/config/config_client.rb", "lib/config/config_client_cluster.rb", "lib/config/config_client_clusters.rb", "lib/config/config_client_host.rb", "lib/config/config_daemon.rb", "lib/config/config_daemon_service.rb", "lib/config/config_daemon_service_dependency.rb", "lib/config/config_daemon_services.rb", "lib/config/config_hook.rb", "lib/config/config_node.rb", "lib/config/config_smtp.rb", "lib/events.rb", "lib/log.rb", "lib/net/net_api.rb", "lib/net/net_client.rb", "lib/net/net_connection.rb", "lib/net/net_server.rb", "lib/net/net_server_client.rb", "lib/net/net_util.rb", "lib/process_util.rb", "lib/pw_app.rb", "lib/service.rb", "lib/service_classes/dummy_service.rb", "lib/service_classes/pid_service.rb", "lib/service_classes/process_service.rb", "lib/service_classes/upstart_service.rb", "lib/service_mgr.rb", "lib/util.rb"]
16
+ s.files = ["README", "Rakefile", "bin/pw", "lib/config/config.rb", "lib/config/config_auth.rb", "lib/config/config_client.rb", "lib/config/config_client_cluster.rb", "lib/config/config_client_clusters.rb", "lib/config/config_client_host.rb", "lib/config/config_daemon.rb", "lib/config/config_daemon_service.rb", "lib/config/config_daemon_service_dependency.rb", "lib/config/config_daemon_services.rb", "lib/config/config_hook.rb", "lib/config/config_node.rb", "lib/config/config_smtp.rb", "lib/events.rb", "lib/log.rb", "lib/net/net_api.rb", "lib/net/net_client.rb", "lib/net/net_connection.rb", "lib/net/net_server.rb", "lib/net/net_server_client.rb", "lib/net/net_util.rb", "lib/process_util.rb", "lib/pw_app.rb", "lib/service.rb", "lib/service_classes/dummy_service.rb", "lib/service_classes/pid_service.rb", "lib/service_classes/process_service.rb", "lib/service_classes/upstart_service.rb", "lib/service_mgr.rb", "lib/util.rb", "Manifest", "processwanker.gemspec"]
17
+ s.homepage = %q{http://codepuppies.com/~ben/pw}
18
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Processwanker", "--main", "README"]
19
+ s.require_paths = ["lib"]
20
+ s.rubyforge_project = %q{processwanker}
21
+ s.rubygems_version = %q{1.6.2}
22
+ s.signing_key = %q{/Users/ben/gem_certs/gem-private_key.pem}
23
+ s.summary = %q{Process monitoring and remote control system}
24
+
25
+ if s.respond_to? :specification_version then
26
+ s.specification_version = 3
27
+
28
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
29
+ s.add_runtime_dependency(%q<echoe>, [">= 0"])
30
+ else
31
+ s.add_dependency(%q<echoe>, [">= 0"])
32
+ end
33
+ else
34
+ s.add_dependency(%q<echoe>, [">= 0"])
35
+ end
36
+ end
data.tar.gz.sig ADDED
Binary file