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.
- data/Manifest +34 -0
- data/README +11 -0
- data/Rakefile +13 -0
- data/bin/pw +5 -0
- data/lib/config/config.rb +96 -0
- data/lib/config/config_auth.rb +208 -0
- data/lib/config/config_client.rb +75 -0
- data/lib/config/config_client_cluster.rb +66 -0
- data/lib/config/config_client_clusters.rb +62 -0
- data/lib/config/config_client_host.rb +152 -0
- data/lib/config/config_daemon.rb +94 -0
- data/lib/config/config_daemon_service.rb +84 -0
- data/lib/config/config_daemon_service_dependency.rb +59 -0
- data/lib/config/config_daemon_services.rb +89 -0
- data/lib/config/config_hook.rb +40 -0
- data/lib/config/config_node.rb +160 -0
- data/lib/config/config_smtp.rb +103 -0
- data/lib/events.rb +224 -0
- data/lib/log.rb +88 -0
- data/lib/net/net_api.rb +189 -0
- data/lib/net/net_client.rb +107 -0
- data/lib/net/net_connection.rb +167 -0
- data/lib/net/net_server.rb +232 -0
- data/lib/net/net_server_client.rb +84 -0
- data/lib/net/net_util.rb +205 -0
- data/lib/process_util.rb +216 -0
- data/lib/pw_app.rb +557 -0
- data/lib/service.rb +512 -0
- data/lib/service_classes/dummy_service.rb +88 -0
- data/lib/service_classes/pid_service.rb +126 -0
- data/lib/service_classes/process_service.rb +218 -0
- data/lib/service_classes/upstart_service.rb +103 -0
- data/lib/service_mgr.rb +226 -0
- data/lib/util.rb +31 -0
- data/processwanker.gemspec +36 -0
- data.tar.gz.sig +0 -0
- metadata +157 -0
- metadata.gz.sig +0 -0
data/lib/pw_app.rb
ADDED
@@ -0,0 +1,557 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
############################################################################
|
3
|
+
#
|
4
|
+
# pw_app.rb
|
5
|
+
#
|
6
|
+
# main commandline entry-point for pw application
|
7
|
+
#
|
8
|
+
############################################################################
|
9
|
+
|
10
|
+
############################################################################
|
11
|
+
#
|
12
|
+
# set load paths
|
13
|
+
#
|
14
|
+
############################################################################
|
15
|
+
|
16
|
+
MY_PATH=File.expand_path(File.dirname(__FILE__))
|
17
|
+
$LOAD_PATH << MY_PATH
|
18
|
+
$LOAD_PATH << File.join( MY_PATH , "config" )
|
19
|
+
$LOAD_PATH << File.join( MY_PATH , "net" )
|
20
|
+
|
21
|
+
############################################################################
|
22
|
+
#
|
23
|
+
# requires
|
24
|
+
#
|
25
|
+
############################################################################
|
26
|
+
|
27
|
+
require 'service_mgr'
|
28
|
+
require 'process_util'
|
29
|
+
require 'config'
|
30
|
+
require 'net_util'
|
31
|
+
require 'net_server'
|
32
|
+
require 'net_client'
|
33
|
+
require 'optparse'
|
34
|
+
require 'log'
|
35
|
+
require 'thread'
|
36
|
+
|
37
|
+
require 'rubygems'
|
38
|
+
gem 'highline'
|
39
|
+
require 'highline'
|
40
|
+
|
41
|
+
module ProcessWanker
|
42
|
+
|
43
|
+
############################################################################
|
44
|
+
#
|
45
|
+
# the main ProcessWanker application
|
46
|
+
#
|
47
|
+
############################################################################
|
48
|
+
|
49
|
+
class Application
|
50
|
+
include Log
|
51
|
+
|
52
|
+
DEFAULT_WAIT=60
|
53
|
+
|
54
|
+
############################################################################
|
55
|
+
#
|
56
|
+
#
|
57
|
+
#
|
58
|
+
############################################################################
|
59
|
+
|
60
|
+
def initialize()
|
61
|
+
|
62
|
+
#
|
63
|
+
# parse options
|
64
|
+
#
|
65
|
+
|
66
|
+
parse_opts()
|
67
|
+
|
68
|
+
#
|
69
|
+
# generate certs/keys?
|
70
|
+
#
|
71
|
+
|
72
|
+
if(@options[:generate_ca])
|
73
|
+
generate_ca()
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
|
77
|
+
if(@options[:generate_user])
|
78
|
+
generate_user()
|
79
|
+
exit
|
80
|
+
end
|
81
|
+
|
82
|
+
Config::set_config_path(@options[:config]) if(@options[:config])
|
83
|
+
|
84
|
+
#
|
85
|
+
# run as daemon?
|
86
|
+
#
|
87
|
+
|
88
|
+
if(@options[:daemon])
|
89
|
+
start_daemon()
|
90
|
+
exit
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# list known clusters/hosts?
|
95
|
+
#
|
96
|
+
|
97
|
+
cp=Config::get_config_path
|
98
|
+
raise "no such config file: #{cp}" if(!File.exist?(cp))
|
99
|
+
@config=Config::load_config(cp) # .new( :client, File.read(cp) )
|
100
|
+
raise "no client config provided" if(!@config.client)
|
101
|
+
if(@options[:list_clusters])
|
102
|
+
list_clusters()
|
103
|
+
exit
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# ok... it's a remote command
|
108
|
+
#
|
109
|
+
|
110
|
+
execute_remote()
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
############################################################################
|
115
|
+
#
|
116
|
+
#
|
117
|
+
#
|
118
|
+
############################################################################
|
119
|
+
|
120
|
+
def parse_opts()
|
121
|
+
|
122
|
+
options={}
|
123
|
+
options[:actions]=[]
|
124
|
+
|
125
|
+
optparse=OptionParser.new do |opts|
|
126
|
+
|
127
|
+
opts.on(nil,'--help','Display this screen') do
|
128
|
+
puts
|
129
|
+
puts opts
|
130
|
+
puts
|
131
|
+
puts " SERVICE_SPEC can be a 'all', a name, a regular expression, or a tag - eg. tag:db"
|
132
|
+
puts " It can also be a comma-separated list of all of the above. Prefixing a string/regex/tag with"
|
133
|
+
puts " a tilde (~) has the meaning 'except'."
|
134
|
+
puts
|
135
|
+
exit
|
136
|
+
end
|
137
|
+
|
138
|
+
opts.on("-l","--list [SERVICE_SPEC]","Show the status of the given service(s)") do |v|
|
139
|
+
options[:actions] << { :cmd => :list, :spec => v || "all" }
|
140
|
+
end
|
141
|
+
|
142
|
+
opts.on("-s","--start SERVICE_SPEC","Start the given service(s)") do |v|
|
143
|
+
options[:actions] << { :cmd => :start, :spec => v || "all" }
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on("-k","--kill SERVICE_SPEC","Stop the given service(s)") do |v|
|
147
|
+
options[:actions] << { :cmd => :stop, :spec => v || "all" }
|
148
|
+
end
|
149
|
+
|
150
|
+
opts.on("-i","--ignore SERVICE_SPEC","Ignore the given service(s)") do |v|
|
151
|
+
options[:actions] << { :cmd => :ignore, :spec => v || "all" }
|
152
|
+
end
|
153
|
+
|
154
|
+
opts.on("--stop SERVICE_SPEC","Stop the given service(s)") do |v|
|
155
|
+
options[:actions] << { :cmd => :stop, :spec => v || "all" }
|
156
|
+
end
|
157
|
+
|
158
|
+
opts.on("-r","--restart SERVICE_SPEC","Restart the given service(s)") do |v|
|
159
|
+
options[:actions] << { :cmd => :restart, :spec => v || "all" }
|
160
|
+
end
|
161
|
+
|
162
|
+
opts.on("-w","--wait SECS",Integer,"Wait SECS seconds for service(s) to reach requested state, default (#{DEFAULT_WAIT})") do |v|
|
163
|
+
options[:wait] = v
|
164
|
+
end
|
165
|
+
|
166
|
+
opts.on("--sequential [DELAY_SECS]",Float,"Execute all operations sequentially, with optional delay between them") do |v|
|
167
|
+
options[:sequential]=v || 0.0
|
168
|
+
end
|
169
|
+
|
170
|
+
opts.on("--reload","Reload configuration") do
|
171
|
+
options[:actions] << { :cmd => :reload }
|
172
|
+
end
|
173
|
+
|
174
|
+
opts.on("--terminate","Terminate daemon") do
|
175
|
+
options[:actions] << { :cmd => :terminate }
|
176
|
+
end
|
177
|
+
|
178
|
+
opts.on("-c","--cluster NAME","Select a cluster to control") do |v|
|
179
|
+
options[:cluster]=v
|
180
|
+
end
|
181
|
+
|
182
|
+
opts.on("-h","--host HOST","Select a single host to control") do |v|
|
183
|
+
options[:host]=v
|
184
|
+
end
|
185
|
+
|
186
|
+
opts.on("-g","--config FILE","Specify configuration file to use (#{Config::get_config_path})") do |v|
|
187
|
+
options[:config]=v
|
188
|
+
end
|
189
|
+
|
190
|
+
opts.on("--list-clusters","Display a list of known clusters and hosts") do
|
191
|
+
options[:list_clusters]=true
|
192
|
+
end
|
193
|
+
|
194
|
+
opts.on("-d","--daemon","Run in the background as a server") do
|
195
|
+
options[:daemon]=true
|
196
|
+
end
|
197
|
+
|
198
|
+
opts.on("-f","--foreground","Don't detach - stay in the foreground (useful only with -d option)") do
|
199
|
+
options[:foreground]=true
|
200
|
+
end
|
201
|
+
|
202
|
+
opts.on("--notboot","Don't start all local services automatically (useful only with the -d option)") do
|
203
|
+
options[:notboot]=true
|
204
|
+
end
|
205
|
+
|
206
|
+
opts.on("--generate-ca","Generate CA cert/key (requires --output-prefix)") do |v|
|
207
|
+
options[:generate_ca]=v
|
208
|
+
end
|
209
|
+
|
210
|
+
opts.on("--output-prefix PATH","Specify output file prefix for .crt and .key") do |v|
|
211
|
+
options[:output_prefix]=v
|
212
|
+
end
|
213
|
+
|
214
|
+
opts.on("--ca-prefix PATH","Specify input file location prefix for the CA certificate and key") do |v|
|
215
|
+
options[:ca_prefix]=v
|
216
|
+
end
|
217
|
+
|
218
|
+
opts.on("--require-passphrase","Use a passphrase to encrypt any generated private keys") do
|
219
|
+
options[:require_passphrase]=true
|
220
|
+
end
|
221
|
+
|
222
|
+
opts.on("--generate-user NAME","Generate client cert/key - requires (--ca-prefix and --output-prefix)") do |v|
|
223
|
+
options[:generate_user]=v
|
224
|
+
end
|
225
|
+
|
226
|
+
opts.on("--debug","Debug hook") do |v|
|
227
|
+
options[:actions] << { :cmd => :debug }
|
228
|
+
end
|
229
|
+
|
230
|
+
opts.on("--log FILE","Log file (for pw output only)") do |v|
|
231
|
+
options[:log_file] = v
|
232
|
+
end
|
233
|
+
|
234
|
+
opts.on("--verbose","Debugging enabled") do |v|
|
235
|
+
Log::set_level(Log::DEBUG)
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
extra=optparse.parse!
|
242
|
+
|
243
|
+
if(extra.length > 0)
|
244
|
+
puts "warning: ignoring extra params: #{extra.join(" ")}"
|
245
|
+
end
|
246
|
+
|
247
|
+
@options=options
|
248
|
+
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
############################################################################
|
253
|
+
#
|
254
|
+
#
|
255
|
+
#
|
256
|
+
############################################################################
|
257
|
+
|
258
|
+
def generate_ca()
|
259
|
+
raise "no --output-prefix specified" if(!@options[:output_prefix])
|
260
|
+
pass=nil
|
261
|
+
if(@options[:require_passphrase])
|
262
|
+
pass=HighLine.new.ask("Password (for encrypting output key) : ") { |x| x.echo=false }.strip
|
263
|
+
p2=HighLine.new.ask("(verify) : ") { |x| x.echo=false }.strip
|
264
|
+
raise "passwords don't match" if(pass != p2)
|
265
|
+
end
|
266
|
+
|
267
|
+
ProcessWanker::NetUtil::generate_ca(@options[:output_prefix],pass)
|
268
|
+
end
|
269
|
+
|
270
|
+
############################################################################
|
271
|
+
#
|
272
|
+
#
|
273
|
+
#
|
274
|
+
############################################################################
|
275
|
+
|
276
|
+
def generate_user()
|
277
|
+
raise "no --output-prefix specified" if(!@options[:output_prefix])
|
278
|
+
raise "no --ca-prefix specified" if(!@options[:ca_prefix])
|
279
|
+
pass=nil
|
280
|
+
if(@options[:require_passphrase])
|
281
|
+
pass=HighLine.new.ask("Password (for encrypting output key) : ") { |x| x.echo=false }.strip
|
282
|
+
p2=HighLine.new.ask("(verify) : ") { |x| x.echo=false }.strip
|
283
|
+
raise "passwords don't match" if(pass != p2)
|
284
|
+
end
|
285
|
+
|
286
|
+
ProcessWanker::NetUtil::generate_cert(
|
287
|
+
@options[:ca_prefix],
|
288
|
+
@options[:output_prefix],
|
289
|
+
@options[:generate_user],
|
290
|
+
pass)
|
291
|
+
|
292
|
+
end
|
293
|
+
|
294
|
+
############################################################################
|
295
|
+
#
|
296
|
+
#
|
297
|
+
#
|
298
|
+
############################################################################
|
299
|
+
|
300
|
+
def start_daemon
|
301
|
+
|
302
|
+
info("starting daemon")
|
303
|
+
|
304
|
+
# create servicemgr
|
305
|
+
ServiceMgr.new()
|
306
|
+
|
307
|
+
# load daemon config
|
308
|
+
puts Config::get_config_path()
|
309
|
+
# @config=Config.new( :daemon, File.read( Config::get_config_path ) )
|
310
|
+
@config=Config::load_config( Config::get_config_path ) # .new( :client, File.read(cp) )
|
311
|
+
raise "no daemon config provided" if(!@config.daemon)
|
312
|
+
|
313
|
+
# apply config
|
314
|
+
ServiceMgr::instance().apply_config(@config,@options[:notboot])
|
315
|
+
|
316
|
+
# daemonize?
|
317
|
+
if(!@options[:foreground])
|
318
|
+
|
319
|
+
puts "Going to background..."
|
320
|
+
|
321
|
+
Process.fork do
|
322
|
+
|
323
|
+
# start net server
|
324
|
+
NetServer.new(@config)
|
325
|
+
|
326
|
+
|
327
|
+
# redirect inputs/outputs
|
328
|
+
file=@options[:log_file] || @config.daemon.log_file || "/var/log/pw.log"
|
329
|
+
puts "logging to #{file}"
|
330
|
+
begin
|
331
|
+
FileUtils.mkdir_p( File.dirname(file) )
|
332
|
+
File.open(file,"a") { |x| x.puts "ProcessWanker starts" }
|
333
|
+
STDERR.reopen(file,"a")
|
334
|
+
STDOUT.reopen(file,"a")
|
335
|
+
STDIN.reopen("/dev/null")
|
336
|
+
rescue Exception => e
|
337
|
+
raise "unable to open log file #{file}"
|
338
|
+
end
|
339
|
+
|
340
|
+
Log::set_level(Log::DEBUG)
|
341
|
+
|
342
|
+
# start new session
|
343
|
+
Process.setsid()
|
344
|
+
|
345
|
+
# run
|
346
|
+
ServiceMgr::instance().run()
|
347
|
+
|
348
|
+
end
|
349
|
+
|
350
|
+
else
|
351
|
+
|
352
|
+
# start net server
|
353
|
+
NetServer.new(@config)
|
354
|
+
|
355
|
+
|
356
|
+
# just run in foreground
|
357
|
+
ServiceMgr::instance().run()
|
358
|
+
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
end
|
363
|
+
|
364
|
+
############################################################################
|
365
|
+
#
|
366
|
+
#
|
367
|
+
#
|
368
|
+
############################################################################
|
369
|
+
|
370
|
+
def match_hosts(hostspec,cluster_name)
|
371
|
+
cluster_name ||= "default"
|
372
|
+
cluster=@config.client.get_cluster(cluster_name)
|
373
|
+
raise "no such cluster: #{cluster_name}" if(!cluster)
|
374
|
+
|
375
|
+
hosts=[]
|
376
|
+
cluster.hosts.each { |k,v| hosts << v if(!hostspec || v.matches_spec(hostspec)) }
|
377
|
+
hosts
|
378
|
+
end
|
379
|
+
|
380
|
+
############################################################################
|
381
|
+
#
|
382
|
+
#
|
383
|
+
#
|
384
|
+
############################################################################
|
385
|
+
|
386
|
+
def list_clusters()
|
387
|
+
|
388
|
+
# ensure that at least default cluster exists
|
389
|
+
@config.client.get_cluster("default")
|
390
|
+
|
391
|
+
puts "clusters:"
|
392
|
+
@config.client.clusters.clusters.each do |cname,cluster|
|
393
|
+
puts " [#{cname}]"
|
394
|
+
c=cluster
|
395
|
+
c.hosts.each do |hname,h|
|
396
|
+
printf(" %-30s",hname)
|
397
|
+
printf(" %-30s","#{h.hostname}:#{h.port}")
|
398
|
+
h.tags.each { |t| printf("%s ",t) }
|
399
|
+
puts
|
400
|
+
end
|
401
|
+
end
|
402
|
+
exit
|
403
|
+
end
|
404
|
+
|
405
|
+
############################################################################
|
406
|
+
#
|
407
|
+
#
|
408
|
+
#
|
409
|
+
############################################################################
|
410
|
+
|
411
|
+
def execute_remote()
|
412
|
+
|
413
|
+
#
|
414
|
+
# match hosts
|
415
|
+
#
|
416
|
+
|
417
|
+
hosts=match_hosts(@options[:host],@options[:cluster])
|
418
|
+
|
419
|
+
#
|
420
|
+
# set defaults
|
421
|
+
#
|
422
|
+
|
423
|
+
if(@options[:actions].length == 0)
|
424
|
+
@options[:actions] << { :cmd => :list, :spec => "all" }
|
425
|
+
end
|
426
|
+
@options[:wait] ||= DEFAULT_WAIT
|
427
|
+
|
428
|
+
#
|
429
|
+
# execute each action sequentially
|
430
|
+
#
|
431
|
+
|
432
|
+
@options[:actions].each do |action|
|
433
|
+
|
434
|
+
#
|
435
|
+
# hit each host in turn, or concurrently
|
436
|
+
#
|
437
|
+
|
438
|
+
if(@options[:sequential])
|
439
|
+
hosts.each do |host|
|
440
|
+
execute_host_action(host,action)
|
441
|
+
end
|
442
|
+
else
|
443
|
+
threads=[]
|
444
|
+
hosts.each do |host|
|
445
|
+
threads << Thread.new do
|
446
|
+
execute_host_action(host,action)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
threads.each { |t| t.join }
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
end
|
454
|
+
|
455
|
+
############################################################################
|
456
|
+
#
|
457
|
+
#
|
458
|
+
#
|
459
|
+
############################################################################
|
460
|
+
|
461
|
+
def execute_host_action(host,action)
|
462
|
+
begin
|
463
|
+
client=NetClient.new(host)
|
464
|
+
req=action.merge(
|
465
|
+
{
|
466
|
+
:wait => @options[:wait],
|
467
|
+
:sequential => @options[:sequential]
|
468
|
+
})
|
469
|
+
client.send_msg(req)
|
470
|
+
resp=client.wait
|
471
|
+
rescue Exception => e
|
472
|
+
puts("[#{host.name}]: connection failure")
|
473
|
+
debug(e.message)
|
474
|
+
e.backtrace { |x| debug(x) }
|
475
|
+
end
|
476
|
+
|
477
|
+
if(resp)
|
478
|
+
display_response(resp,host)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
############################################################################
|
483
|
+
#
|
484
|
+
#
|
485
|
+
#
|
486
|
+
############################################################################
|
487
|
+
|
488
|
+
@@display_mutex=Mutex.new()
|
489
|
+
|
490
|
+
def display_response(resp,host)
|
491
|
+
|
492
|
+
@@display_mutex.synchronize do
|
493
|
+
|
494
|
+
if(resp[:services])
|
495
|
+
puts
|
496
|
+
printf("[%s]",host.name)
|
497
|
+
if(host.tags.length>0)
|
498
|
+
puts " - (#{host.tags.join(",")})"
|
499
|
+
else
|
500
|
+
puts
|
501
|
+
end
|
502
|
+
printf(" %-30s %-30s %-20s %s\n","name","state","want-state","tags")
|
503
|
+
groups=resp[:services].values.map { |x| x[:group_name] }.uniq.sort
|
504
|
+
groups.each do |grp|
|
505
|
+
puts " [group: #{grp}]" if(groups.length > 1)
|
506
|
+
services=resp[:services].values.select { |x| x[:group_name] == grp }
|
507
|
+
services.sort! { |a,b| a[:name] <=> b[:name] }
|
508
|
+
services.each do |s|
|
509
|
+
show=s[:show_state]
|
510
|
+
show << " (suppressed)" if(s[:suppress])
|
511
|
+
printf(" %-30s %-30s %-20s %s\n",
|
512
|
+
s[:name],
|
513
|
+
show,
|
514
|
+
s[:want_state],
|
515
|
+
s[:tags].join(" ")
|
516
|
+
)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
puts
|
521
|
+
end
|
522
|
+
|
523
|
+
if(resp[:counts])
|
524
|
+
resp[:counts].keys.sort.each do |name|
|
525
|
+
printf("%-50s %d\n",name,resp[:counts][name])
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
end
|
530
|
+
|
531
|
+
end
|
532
|
+
|
533
|
+
############################################################################
|
534
|
+
#
|
535
|
+
#
|
536
|
+
#
|
537
|
+
############################################################################
|
538
|
+
|
539
|
+
############################################################################
|
540
|
+
#
|
541
|
+
#
|
542
|
+
#
|
543
|
+
############################################################################
|
544
|
+
|
545
|
+
end
|
546
|
+
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
############################################################################
|
551
|
+
#
|
552
|
+
#
|
553
|
+
#
|
554
|
+
############################################################################
|
555
|
+
|
556
|
+
ProcessWanker::Application.new()
|
557
|
+
|