bluepill 0.0.46 → 0.0.47

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  module Application
3
4
  module Client
4
-
5
+
5
6
  end
6
7
  end
7
8
  end
@@ -1,7 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
2
3
  module Application
3
4
  module ServerMethods
4
-
5
+
5
6
  def status
6
7
  self.processes.collect do |process|
7
8
  "#{process.name} #{process.state}"
@@ -9,12 +10,12 @@ module Bluepill
9
10
  end
10
11
 
11
12
  def restart
12
- self.socket = Bluepill::Socket.new(name, base_dir).client
13
+ self.socket = Bluepill::Socket.new(name, base_dir).client
13
14
  socket.send("restart\n", 0)
14
15
  end
15
16
 
16
17
  def stop
17
- self.socket = Bluepill::Socket.new(name, base_dir).client
18
+ self.socket = Bluepill::Socket.new(name, base_dir).client
18
19
  socket.send("stop\n", 0)
19
20
  end
20
21
  end
@@ -1,55 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  module Bluepill
3
+ class HistoryValue < Struct.new(:value, :critical)
4
+ end
5
+
2
6
  class ConditionWatch
3
7
  attr_accessor :logger, :name
4
8
  EMPTY_ARRAY = [].freeze # no need to recreate one every tick
5
-
9
+
6
10
  def initialize(name, options = {})
7
11
  @name = name
8
12
 
9
- @logger = options.delete(:logger)
10
- @fires = options.has_key?(:fires) ? Array(options.delete(:fires)) : [:restart]
11
- @every = options.delete(:every)
12
- @times = options.delete(:times) || [1,1]
13
- @times = [@times, @times] unless @times.is_a?(Array) # handles :times => 5
13
+ @logger = options.delete(:logger)
14
+ @fires = options.has_key?(:fires) ? Array(options.delete(:fires)) : [:restart]
15
+ @every = options.delete(:every)
16
+ @times = options.delete(:times) || [1,1]
17
+ @times = [@times, @times] unless @times.is_a?(Array) # handles :times => 5
14
18
 
15
19
  self.clear_history!
16
-
20
+
17
21
  @process_condition = ProcessConditions[@name].new(options)
18
22
  end
19
-
23
+
20
24
  def run(pid, tick_number = Time.now.to_i)
21
25
  if @last_ran_at.nil? || (@last_ran_at + @every) <= tick_number
22
26
  @last_ran_at = tick_number
23
- self.record_value(@process_condition.run(pid))
27
+
28
+ value = @process_condition.run(pid)
29
+ @history << HistoryValue.new(@process_condition.format_value(value), @process_condition.check(value))
30
+ self.logger.info(self.to_s)
31
+
24
32
  return @fires if self.fired?
25
33
  end
26
34
  EMPTY_ARRAY
27
35
  end
28
-
29
- def record_value(value)
30
- # TODO: record value in ProcessStatistics
31
- @history[@history_index] = [value, @process_condition.check(value)]
32
- @history_index = (@history_index + 1) % @history.size
33
- self.logger.info(self.to_s)
34
- end
35
-
36
+
36
37
  def clear_history!
37
- @last_ran_at = nil
38
- @history = Array.new(@times[1])
39
- @history_index = 0
38
+ @history = Util::RotationalArray.new(@times.last)
40
39
  end
41
-
40
+
42
41
  def fired?
43
- @history.select {|v| v && !v[1]}.size >= @times[0]
42
+ @history.count {|v| not v.critical} >= @times.first
44
43
  end
45
-
44
+
46
45
  def to_s
47
- # TODO: this will be out of order because of the way history values are assigned
48
- # use (@history[(@history_index - 1)..1] + @history[0..(@history_index - 1)]).
49
- # collect {|v| "#{v[0]}#{v[1] ? '' : '*'}"}.join(", ")
50
- # but that's gross so... it's gonna be out of order till we figure out a better way to get it in order
51
- data = @history.collect {|v| "#{@process_condition.format_value(v[0])}#{v[1] ? '' : '*'}" if v}.compact.join(", ")
52
- "#{@name}: [#{data}]"
46
+ data = @history.collect {|v| "#{v.value}#{'*' unless v.critical}"}.join(", ")
47
+ "#{@name}: [#{data}]\n"
53
48
  end
54
49
  end
55
- end
50
+ end
@@ -1,23 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'fileutils'
2
3
 
3
4
  module Bluepill
4
5
  class Controller
5
6
  attr_accessor :base_dir, :log_file, :sockets_dir, :pids_dir
6
-
7
+
7
8
  def initialize(options = {})
8
9
  self.log_file = options[:log_file]
9
10
  self.base_dir = options[:base_dir]
10
11
  self.sockets_dir = File.join(base_dir, 'socks')
11
12
  self.pids_dir = File.join(base_dir, 'pids')
12
-
13
+
13
14
  setup_dir_structure
14
15
  cleanup_bluepill_directory
15
16
  end
16
-
17
+
17
18
  def running_applications
18
19
  Dir[File.join(sockets_dir, "*.sock")].map{|x| File.basename(x, ".sock")}
19
20
  end
20
-
21
+
21
22
  def handle_command(application, command, *args)
22
23
  case command.to_sym
23
24
  when :status
@@ -44,10 +45,10 @@ module Bluepill
44
45
  when :log
45
46
  log_file_location = self.send_to_daemon(application, :log_file)
46
47
  log_file_location = self.log_file if log_file_location.to_s.strip.empty?
47
-
48
+
48
49
  requested_pattern = args.first
49
50
  grep_pattern = self.grep_pattern(application, requested_pattern)
50
-
51
+
51
52
  tail = "tail -n 100 -f #{log_file_location} | grep -E '#{grep_pattern}'"
52
53
  puts "Tailing log for #{requested_pattern}..."
53
54
  Kernel.exec(tail)
@@ -56,7 +57,7 @@ module Bluepill
56
57
  exit(1)
57
58
  end
58
59
  end
59
-
60
+
60
61
  def send_to_daemon(application, command, *args)
61
62
  begin
62
63
  verify_version!(application)
@@ -76,13 +77,13 @@ module Bluepill
76
77
  abort("Connection Refused: Server is not running")
77
78
  end
78
79
  end
79
-
80
+
80
81
  def grep_pattern(application, query = nil)
81
82
  pattern = [application, query].compact.join(':')
82
83
  ['\[.*', Regexp.escape(pattern), '.*'].compact.join
83
84
  end
84
85
  private
85
-
86
+
86
87
  def cleanup_bluepill_directory
87
88
  self.running_applications.each do |app|
88
89
  pid = pid_for(app)
@@ -94,18 +95,18 @@ module Bluepill
94
95
  end
95
96
  end
96
97
  end
97
-
98
+
98
99
  def pid_for(app)
99
100
  pid_file = File.join(self.pids_dir, "#{app}.pid")
100
101
  File.exists?(pid_file) && File.read(pid_file).to_i
101
102
  end
102
-
103
+
103
104
  def setup_dir_structure
104
105
  [@sockets_dir, @pids_dir].each do |dir|
105
106
  FileUtils.mkdir_p(dir) unless File.exists?(dir)
106
107
  end
107
108
  end
108
-
109
+
109
110
  def verify_version!(application)
110
111
  begin
111
112
  version = Socket.client_command(base_dir, application, "version")
data/lib/bluepill/dsl.rb CHANGED
@@ -1,155 +1,8 @@
1
- require 'ostruct'
1
+ # -*- encoding: utf-8 -*-
2
2
  module Bluepill
3
- def self.define_process_condition(name, &block)
4
- klass = Class.new(ProcessConditions::ProcessCondition, &block)
5
- ProcessConditions.const_set("#{name.to_s.camelcase}", klass)
6
- end
7
-
8
3
  def self.application(app_name, options = {}, &block)
9
- app = Application.new(app_name.to_s, options, &block)
10
-
11
- process_proxy = Class.new do
12
- attr_reader :attributes, :watches
13
- def initialize(process_name = nil)
14
- @name = process_name
15
- @attributes = {}
16
- @watches = {}
17
- end
18
-
19
- def method_missing(name, *args)
20
- if args.size == 1 && name.to_s =~ /^(.*)=$/
21
- @attributes[$1.to_sym] = args.first
22
- elsif args.empty? && @attributes.key?(name.to_sym)
23
- @attributes[name.to_sym]
24
- else
25
- super
26
- end
27
- end
28
-
29
- def checks(name, options = {})
30
- @watches[name] = options
31
- end
32
-
33
- def validate_child_process(child)
34
- unless child.attributes.has_key?(:stop_command)
35
- $stderr.puts "Config Error: Invalid child process monitor for #{@name}"
36
- $stderr.puts "You must specify a stop command to monitor child processes."
37
- exit(6)
38
- end
39
- end
40
-
41
- def create_child_process_template
42
- if @child_process_block
43
- child_proxy = self.class.new
44
- # Children inherit some properties of the parent
45
- [:start_grace_time, :stop_grace_time, :restart_grace_time].each do |attribute|
46
- child_proxy.send("#{attribute}=", @attributes[attribute]) if @attributes.key?(attribute)
47
- end
48
- @child_process_block.call(child_proxy)
49
- validate_child_process(child_proxy)
50
- @attributes[:child_process_template] = child_proxy.to_process(nil)
51
- end
52
- end
53
-
54
- def monitor_children(&child_process_block)
55
- @child_process_block = child_process_block
56
- @attributes[:monitor_children] = true
57
- end
58
-
59
- def to_process(process_name)
60
- process = Bluepill::Process.new(process_name, @attributes)
61
- @watches.each do |name, opts|
62
- if Bluepill::Trigger[name]
63
- process.add_trigger(name, opts)
64
- else
65
- process.add_watch(name, opts)
66
- end
67
- end
68
-
69
- process
70
- end
71
- end
72
- app_proxy = Class.new do
73
- if RUBY_VERSION >= '1.9'
74
- class_variable_set(:@@app, app)
75
- class_variable_set(:@@process_proxy, process_proxy)
76
- class_variable_set(:@@process_keys, Hash.new) # because I don't want to require Set just for validations
77
- class_variable_set(:@@pid_files, Hash.new)
78
- else
79
- @@app = app
80
- @@process_proxy = process_proxy
81
- @@process_keys = Hash.new
82
- @@pid_files = Hash.new
83
- end
84
- attr_accessor :working_dir, :uid, :gid, :environment
85
-
86
- def validate_process(process, process_name)
87
- # validate uniqueness of group:process
88
- process_key = [process.attributes[:group], process_name].join(":")
89
- if @@process_keys.key?(process_key)
90
- $stderr.print "Config Error: You have two entries for the process name '#{process_name}'"
91
- $stderr.print " in the group '#{process.attributes[:group]}'" if process.attributes.key?(:group)
92
- $stderr.puts
93
- exit(6)
94
- else
95
- @@process_keys[process_key] = 0
96
- end
97
-
98
- # validate required attributes
99
- [:start_command].each do |required_attr|
100
- if !process.attributes.key?(required_attr)
101
- $stderr.puts "Config Error: You must specify a #{required_attr} for '#{process_name}'"
102
- exit(6)
103
- end
104
- end
105
-
106
- # validate uniqueness of pid files
107
- pid_key = process.pid_file.strip
108
- if @@pid_files.key?(pid_key)
109
- $stderr.puts "Config Error: You have two entries with the pid file: #{process.pid_file}"
110
- exit(6)
111
- else
112
- @@pid_files[pid_key] = 0
113
- end
114
- end
115
-
116
- def process(process_name, &process_block)
117
- process_proxy = @@process_proxy.new(process_name)
118
- process_block.call(process_proxy)
119
- process_proxy.create_child_process_template
120
-
121
- set_app_wide_attributes(process_proxy)
122
-
123
- assign_default_pid_file(process_proxy, process_name)
124
-
125
- validate_process(process_proxy, process_name)
126
-
127
- group = process_proxy.attributes.delete(:group)
128
- process = process_proxy.to_process(process_name)
129
-
130
-
131
-
132
- @@app.add_process(process, group)
133
- end
134
-
135
- def set_app_wide_attributes(process_proxy)
136
- [:working_dir, :uid, :gid, :environment].each do |attribute|
137
- unless process_proxy.attributes.key?(attribute)
138
- process_proxy.attributes[attribute] = self.send(attribute)
139
- end
140
- end
141
- end
142
-
143
- def assign_default_pid_file(process_proxy, process_name)
144
- unless process_proxy.attributes.key?(:pid_file)
145
- group_name = process_proxy.attributes["group"]
146
- default_pid_name = [group_name, process_name].compact.join('_').gsub(/[^A-Za-z0-9_\-]/, "_")
147
- process_proxy.pid_file = File.join(@@app.pids_dir, default_pid_name + ".pid")
148
- end
149
- end
150
- end
151
-
152
- yield(app_proxy.new)
153
- app.load
4
+ app_proxy = AppProxy.new(app_name, options)
5
+ yield(app_proxy)
6
+ app_proxy.app.load
154
7
  end
155
8
  end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class AppProxy
4
+ APP_ATTRIBUTES = [:working_dir, :uid, :gid, :environment]
5
+
6
+ attr_accessor *APP_ATTRIBUTES
7
+ attr_reader :app
8
+
9
+ def initialize(app_name, options)
10
+ @app = Application.new(app_name.to_s, options)
11
+ end
12
+
13
+ def process(process_name, &process_block)
14
+ attributes = {}
15
+ APP_ATTRIBUTES.each {|a| attributes[a] = self.send(a) }
16
+
17
+ process_factory = ProcessFactory.new(attributes, process_block)
18
+
19
+ process = process_factory.create_process(process_name, @app.pids_dir)
20
+ group = process_factory.attributes.delete(:group)
21
+
22
+ @app.add_process(process, group)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,87 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class ProcessFactory
4
+ attr_reader :attributes
5
+
6
+ @@process_keys = Hash.new
7
+ @@pid_files = Hash.new
8
+
9
+ def initialize(attributes, process_block)
10
+ @attributes = attributes
11
+ @process_block = process_block
12
+ end
13
+
14
+ def create_process(name, pids_dir)
15
+ self.assign_default_pid_file(name, pids_dir)
16
+
17
+ process = ProcessProxy.new(name, @attributes, @process_block)
18
+ child_process_block = @attributes.delete(:child_process_block)
19
+ @attributes[:child_process_factory] = ProcessFactory.new(@attributes, child_process_block) if @attributes[:monitor_children]
20
+
21
+ self.validate_process! process
22
+ process.to_process
23
+ end
24
+
25
+ def create_child_process(name, pid, logger)
26
+ attributes = {}
27
+ [:start_grace_time, :stop_grace_time, :restart_grace_time].each {|a| attributes[a] = @attributes[a]}
28
+ attributes[:actual_pid] = pid
29
+ attributes[:logger] = logger
30
+
31
+ child = ProcessProxy.new(name, attributes, @process_block)
32
+ self.validate_child_process! child
33
+ process = child.to_process
34
+
35
+ process.determine_initial_state
36
+ process
37
+ end
38
+
39
+ protected
40
+
41
+ def assign_default_pid_file(process_name, pids_dir)
42
+ unless @attributes.key?(:pid_file)
43
+ group_name = @attributes[:group]
44
+ default_pid_name = [group_name, process_name].compact.join('_').gsub(/[^A-Za-z0-9_\-]/, "_")
45
+ @attributes[:pid_file] = File.join(pids_dir, default_pid_name + ".pid")
46
+ end
47
+ end
48
+
49
+ def validate_process!(process)
50
+ # validate uniqueness of group:process
51
+ process_key = [process.attributes[:group], process.name].join(":")
52
+ if @@process_keys.key?(process_key)
53
+ $stderr.print "Config Error: You have two entries for the process name '#{process.name}'"
54
+ $stderr.print " in the group '#{process.attributes[:group]}'" if process.attributes.key?(:group)
55
+ $stderr.puts
56
+ exit(6)
57
+ else
58
+ @@process_keys[process_key] = 0
59
+ end
60
+
61
+ # validate required attributes
62
+ [:start_command].each do |required_attr|
63
+ if !process.attributes.key?(required_attr)
64
+ $stderr.puts "Config Error: You must specify a #{required_attr} for '#{process.name}'"
65
+ exit(6)
66
+ end
67
+ end
68
+
69
+ # validate uniqueness of pid files
70
+ pid_key = process.attributes[:pid_file].strip
71
+ if @@pid_files.key?(pid_key)
72
+ $stderr.puts "Config Error: You have two entries with the pid file: #{pid_key}"
73
+ exit(6)
74
+ else
75
+ @@pid_files[pid_key] = 0
76
+ end
77
+ end
78
+
79
+ def validate_child_process!(child)
80
+ unless child.attributes.has_key?(:stop_command)
81
+ $stderr.puts "Config Error: Invalid child process monitor for #{child.name}"
82
+ $stderr.puts "You must specify a stop command to monitor child processes."
83
+ exit(6)
84
+ end
85
+ end
86
+ end
87
+ end