processwanker 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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
+