bluepill 0.0.46 → 0.0.47

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.
@@ -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