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/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
+