use GET instead of HEAD
[god.git] / lib / god / conditions / http_response_code.rb
blob8184153159dbb4627fc28c61be0d656dbc45d622
1 require 'net/http'
3 module God
4   module Conditions
5     
6     # Condition Symbol :http_response_code
7     # Type: Poll
8     # 
9     # Trigger based on the response from an HTTP request.
10     #
11     # Paramaters
12     #   Required
13     #     +host+ is the hostname to connect [required]
14     #     --one of code_is or code_is_not--
15     #     +code_is+ trigger if the response code IS one of these
16     #               e.g. 500 or '500' or [404, 500] or %w{404 500}
17     #     +code_is_not+ trigger if the response code IS NOT one of these
18     #                   e.g. 200 or '200' or [200, 302] or %w{200 302}
19     #  Optional
20     #     +port+ is the port to connect (default 80)
21     #     +path+ is the path to connect (default '/')
22     #     +times+ is the number of times after which to trigger (default 1)
23     #             e.g. 3 (times in a row) or [3, 5] (three out of fives times)
24     #     +timeout+ is the time to wait for a connection (default 60.seconds)
25     #
26     # Examples
27     #
28     # Trigger if the response code from www.example.com/foo/bar
29     # is not a 200 (or if the connection is refused or times out:
30     #
31     #   on.condition(:http_response_code) do |c|
32     #     c.host = 'www.example.com'
33     #     c.path = '/foo/bar'
34     #     c.code_is_not = 200
35     #   end
36     #
37     # Trigger if the response code is a 404 or a 500 (will not
38     # be triggered by a connection refusal or timeout):
39     #
40     #   on.condition(:http_response_code) do |c|
41     #     c.host = 'www.example.com'
42     #     c.path = '/foo/bar'
43     #     c.code_is = [404, 500]
44     #   end
45     #
46     # Trigger if the response code is not a 200 five times in a row:
47     #
48     #   on.condition(:http_response_code) do |c|
49     #     c.host = 'www.example.com'
50     #     c.path = '/foo/bar'
51     #     c.code_is_not = 200
52     #     c.times = 5
53     #   end
54     #
55     # Trigger if the response code is not a 200 or does not respond
56     # within 10 seconds:
57     #
58     #   on.condition(:http_response_code) do |c|
59     #     c.host = 'www.example.com'
60     #     c.path = '/foo/bar'
61     #     c.code_is_not = 200
62     #     c.timeout = 10
63     #   end
64     class HttpResponseCode < PollCondition
65       attr_accessor :code_is,      # e.g. 500 or '500' or [404, 500] or %w{404 500}
66                     :code_is_not,  # e.g. 200 or '200' or [200, 302] or %w{200 302}
67                     :times,        # e.g. 3 or [3, 5]
68                     :host,         # e.g. www.example.com
69                     :port,         # e.g. 8080
70                     :timeout,      # e.g. 60.seconds
71                     :path          # e.g. '/'
72       
73       def initialize
74         super
75         self.port = 80
76         self.path = '/'
77         self.times = [1, 1]
78         self.timeout = 60.seconds
79       end
80       
81       def prepare
82         self.code_is = Array(self.code_is).map { |x| x.to_i } if self.code_is
83         self.code_is_not = Array(self.code_is_not).map { |x| x.to_i } if self.code_is_not
84         
85         if self.times.kind_of?(Integer)
86           self.times = [self.times, self.times]
87         end
88         
89         @timeline = Timeline.new(self.times[1])
90         @history = Timeline.new(self.times[1])
91       end
92       
93       def reset
94         @timeline.clear
95         @history.clear
96       end
97       
98       def valid?
99         valid = true
100         valid &= complain("Attribute 'host' must be specified", self) if self.host.nil?
101         valid &= complain("One (and only one) of attributes 'code_is' and 'code_is_not' must be specified", self) if
102           (self.code_is.nil? && self.code_is_not.nil?) || (self.code_is && self.code_is_not)
103         valid
104       end
105       
106       def test
107         response = nil
108         
109         Net::HTTP.start(self.host, self.port) do |http|
110           http.read_timeout = self.timeout
111           response = http.get(self.path)
112         end
113         
114         actual_response_code = response.code.to_i
115         if self.code_is && self.code_is.include?(actual_response_code)
116           pass(actual_response_code)
117         elsif self.code_is_not && !self.code_is_not.include?(actual_response_code)
118           pass(actual_response_code)
119         else
120           fail(actual_response_code)
121         end
122       rescue Errno::ECONNREFUSED
123         self.code_is ? fail('Refused') : pass('Refused')
124       rescue Timeout::Error
125         self.code_is ? fail('Timeout') : pass('Timeout')
126       end
127       
128       private
129       
130       def pass(code)
131         @timeline << true
132         if @timeline.select { |x| x }.size >= self.times.first
133           self.info = "http response abnormal #{history(code, true)}"
134           true
135         else
136           self.info = "http response nominal #{history(code, true)}"
137           false
138         end
139       end
140       
141       def fail(code)
142         @timeline << false
143         self.info = "http response nominal #{history(code, false)}"
144         false
145       end
146       
147       def history(code, passed)
148         entry = code.to_s.dup
149         entry = '*' + entry if passed
150         @history << entry
151         '[' + @history.join(", ") + ']'
152       end
153       
154     end
155     
156   end