Module: Bluepill

Defined in:
lib/bluepill/dsl.rb,
lib/bluepill/group.rb,
lib/bluepill/logger.rb,
lib/bluepill/socket.rb,
lib/bluepill/system.rb,
lib/bluepill/process.rb,
lib/bluepill/trigger.rb,
lib/bluepill/version.rb,
lib/bluepill/controller.rb,
lib/bluepill/application.rb,
lib/bluepill/condition_watch.rb,
lib/bluepill/triggers/flapping.rb,
lib/bluepill/application/client.rb,
lib/bluepill/process_conditions.rb,
lib/bluepill/util/rotational_array.rb,
lib/bluepill/process_conditions/cpu_usage.rb,
lib/bluepill/process_conditions/mem_usage.rb,
lib/bluepill/process_conditions/always_true.rb,
lib/bluepill/process_conditions/process_condition.rb

Defined Under Namespace

Modules: Application, ProcessConditions, Socket, System, Triggers, Util Classes: ConditionWatch, Controller, Group, Logger, Process, Trigger

Constant Summary collapse

VERSION =
"0.0.26"

Class Method Summary collapse

Class Method Details

.application(app_name, options = {}) {|app_proxy.new| ... } ⇒ Object

Yields:

  • (app_proxy.new)


3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/bluepill/dsl.rb', line 3

def self.application(app_name, options = {}, &block)
  app = Application.new(app_name.to_s, options, &block)
 
  process_proxy = Class.new do
    attr_reader :attributes, :watches
    def initialize(process_name = nil)
      @name = process_name
      @attributes = {}
      @watches = {}
    end
    
    def method_missing(name, *args)
      if args.size == 1 && name.to_s =~ /^(.*)=$/
        @attributes[$1.to_sym] = args.first
      elsif args.empty? && @attributes.key?(name.to_sym)
        @attributes[name.to_sym]
      else
        super
      end
    end
    
    def checks(name, options = {})
      @watches[name] = options
    end
    
    def validate_child_process(child)
      unless child.attributes.has_key?(:stop_command)
        $stderr.puts "Config Error: Invalid child process monitor for #{@name}"
        $stderr.puts "You must specify a stop command to monitor child processes."
        exit(6)
      end
    end
    
    def monitor_children(&child_process_block)
      child_proxy = self.class.new
      
      # Children inherit some properties of the parent
      child_proxy.start_grace_time = @attributes[:start_grace_time]
      child_proxy.stop_grace_time = @attributes[:stop_grace_time]
      child_proxy.restart_grace_time = @attributes[:restart_grace_time]
      
      child_process_block.call(child_proxy)
      validate_child_process(child_proxy)
      
      @attributes[:child_process_template] = child_proxy.to_process(nil)
      # @attributes[:child_process_template].freeze
      @attributes[:monitor_children] = true
    end
    
    def to_process(process_name)
      process = Bluepill::Process.new(process_name, @attributes)
      @watches.each do |name, opts|
        if Bluepill::Trigger[name]
          process.add_trigger(name, opts)
        else
          process.add_watch(name, opts)
        end
      end

      process
    end
  end
  
  app_proxy = Class.new do
    @@app = app
    @@process_proxy = process_proxy
    @@process_keys = Hash.new # because I don't want to require Set just for validations
    @@pid_files = Hash.new
    attr_accessor :working_dir, :uid, :gid
    
    def validate_process(process, process_name)
      # validate uniqueness of group:process
      process_key = [process.attributes[:group], process_name].join(":")
      if @@process_keys.key?(process_key)
        $stderr.print "Config Error: You have two entries for the process name '#{process_name}'"
        $stderr.print " in the group '#{process.attributes[:group]}'" if process.attributes.key?(:group)
        $stderr.puts
        exit(6)
      else
        @@process_keys[process_key] = 0
      end
      
      # validate required attributes
      [:start_command].each do |required_attr|
        if !process.attributes.key?(required_attr)
          $stderr.puts "Config Error: You must specify a #{required_attr} for '#{process_name}'"
          exit(6)
        end
      end
      
      # validate uniqueness of pid files
      pid_key = process.pid_file.strip
      if @@pid_files.key?(pid_key)
        $stderr.puts "Config Error: You have two entries with the pid file: #{process.pid_file}"
        exit(6)
      else
        @@pid_files[pid_key] = 0
      end
    end
    
    def process(process_name, &process_block)
      process_proxy = @@process_proxy.new(process_name)
      process_block.call(process_proxy)
      set_app_wide_attributes(process_proxy)
      
      assign_default_pid_file(process_proxy, process_name)
      
      validate_process(process_proxy, process_name)
      
      group = process_proxy.attributes.delete(:group)        
      process = process_proxy.to_process(process_name)
      
      
      
      @@app.add_process(process, group)
    end
    
    def set_app_wide_attributes(process_proxy)
      [:working_dir, :uid, :gid].each do |attribute|
        unless process_proxy.attributes.key?(attribute)
          process_proxy.attributes[attribute] = self.send(attribute)
        end
      end
    end
    
    def assign_default_pid_file(process_proxy, process_name)
      unless process_proxy.attributes.key?(:pid_file)
        group_name = process_proxy.attributes["group"]
        default_pid_name = [group_name, process_name].compact.join('_').gsub(/[^A-Za-z0-9_\-]/, "_")
        process_proxy.pid_file = File.join(@@app.pids_dir, default_pid_name + ".pid")
      end
    end
  end
  
  yield(app_proxy.new)
  app.load
end