outpost 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,11 @@
1
+ == 0.2.1
2
+ * Features
3
+ * Support for :warning status (by github.com/sryche)
4
+ * Subclassing Outpost::Application is no longer required.
5
+ * HTTP Scout responds to response_time now
6
+ * Added scout data to the report (so you can read acquired data)
7
+ * Support for creating scouts without any expectations (good if you're
8
+ interested in the data value but not if they mean system is either up or down)
1
9
 
2
10
  == 0.2
3
11
  * Features
@@ -10,7 +10,7 @@ GEM
10
10
  faraday (0.5.4)
11
11
  addressable (~> 2.2.2)
12
12
  multipart-post (~> 1.1.0)
13
- rack (>= 1.1.0, < 2)
13
+ rack (< 2, >= 1.1.0)
14
14
  i18n (0.5.0)
15
15
  linecache (0.43)
16
16
  linecache19 (0.5.11)
@@ -73,7 +73,7 @@ reporting "down" in that case).
73
73
 
74
74
  ## Outpost
75
75
 
76
- Outpost is the description of the system and provides a DSL to do it.
76
+ Outpost is the description of the system and provides a DSL to do it.
77
77
  Check "How it works" section for an example, or check the [integration tests](https://github.com/vinibaggio/outpost/blob/master/test/integration/basic_application_test.rb)
78
78
  for more.
79
79
 
@@ -110,6 +110,14 @@ method, where you can implement any kind of logic to test whether your system is
110
110
  or not. You may also include expectations in order to process the output of your system.
111
111
  For more information about expectations, check the section below.
112
112
 
113
+ If you're interested in the data the Scouts got through a measurement, you can
114
+ tell Outpost that it must save that data after the measurement is run. That way
115
+ you can inquiry it for further analysis. This way, you can have Scouts without
116
+ any expectations/reports, so you can collect data without telling if the system
117
+ is either up or down. You can check an usage example in the [Reports
118
+ integration
119
+ test](https://github.com/vinibaggio/outpost/blob/master/test/integration/reporting_test.rb).
120
+
113
121
  ## Expectations
114
122
 
115
123
  Consider the following code snippet, taken from previous examples:
@@ -123,7 +131,7 @@ to get Scout's output and evaluate it, in order to determine a status.
123
131
  They must be registered into each Scout that wish to support different types
124
132
  of expectations. You can supply a block or an object that respond to #call
125
133
  and return true if any of the rules match. It will receive an instance
126
- of the scout (so you can query current system state) as the first parameter
134
+ of the scout (so you can query current system state) as the first parameter
127
135
  and the state defined in the #report method as the second.
128
136
 
129
137
  So you can easily create your own expectation. Let's recreate the :response\_code in
@@ -182,6 +190,23 @@ system administrator:
182
190
  # down.
183
191
  outpost.notify if outpost.down?
184
192
 
193
+ ## Creating Outpost applications programatically
194
+
195
+ It is also possible to create Outposts without having to subclass it. Use the
196
+ methods #add_scout and #add_notifier and you're set:
197
+
198
+ outpost = Outpost::Application.new
199
+
200
+ outpost.add_scout Outpost::Scouts::Http => 'master http server' do
201
+ options :host => 'localhost', :port => 9595
202
+ report :up, :response_code => 200
203
+ end
204
+
205
+ outpost.run
206
+
207
+ This is good when you want to have some sort of template (by inheriting from
208
+ Outpost::Application) and then configure things as you go.
209
+
185
210
  ## TODO
186
211
 
187
212
  See [TODO](https://github.com/vinibaggio/outpost/blob/master/TODO.md).
data/TODO.md CHANGED
@@ -2,6 +2,4 @@
2
2
 
3
3
  * SSH Support
4
4
  * Daemon and scheduler
5
- * New status: :warning
6
5
  * Web dashboard
7
- * Better error reporting
@@ -11,14 +11,31 @@ module Outpost
11
11
  # end
12
12
  # end
13
13
  #
14
- # @abstract
14
+ # The above example will set templates and every new instance of
15
+ # ExampleSuccess will have the same behavior. But all the methods
16
+ # are available to instances, so it is also possible to create Outpost
17
+ # applications as the following example:
18
+ #
19
+ # my_outpost = Outpost::Application.new
20
+ # my_outpost.name = 'Example'
21
+ #
22
+ # my_outpost.using(Outpost::Scouts::Http => 'master http server') do
23
+ # options :host => 'localhost', :port => 9595
24
+ # report :up, :response_code => 200
25
+ # end
15
26
  class Application
16
27
  class << self
17
- # Returns all the registered scouts.
18
- attr_reader :scouts
28
+ def scout_templates
29
+ @scout_templates || []
30
+ end
19
31
 
20
- # Returns all the registered notifiers.
21
- attr_reader :notifiers
32
+ def notifier_templates
33
+ @notifier_templates || []
34
+ end
35
+
36
+ def name_template
37
+ @name_template || self.to_s
38
+ end
22
39
 
23
40
  # Register a scout in the list of scouts.
24
41
  #
@@ -27,15 +44,11 @@ module Outpost
27
44
  #
28
45
  # @yield Block to be evaluated to configure the current {Scout}.
29
46
  def using(scout_description, &block)
30
- @scouts ||= Hash.new { |h, k| h[k] = {} }
31
-
32
- config = ScoutConfig.new
33
- config.instance_eval(&block)
34
-
35
- scout_description.each do |scout, description|
36
- @scouts[scout][:description] = description
37
- @scouts[scout][:config] = config
38
- end
47
+ @scout_templates ||= []
48
+ @scout_templates << {
49
+ :scout_description => scout_description,
50
+ :block => block
51
+ }
39
52
  end
40
53
 
41
54
  # Register a notifier class in the list of notifications.
@@ -48,8 +61,8 @@ module Outpost
48
61
  # @param [Hash, #read] options Options that will be used to configure the
49
62
  # notification class.
50
63
  def notify(notifier, options={})
51
- @notifiers ||= {}
52
- @notifiers[notifier] = options
64
+ @notifier_templates ||= []
65
+ @notifier_templates << {:notifier => notifier, :options => options}
53
66
  end
54
67
 
55
68
  # Set the name of the scout. Can be used by notifiers in order to have
@@ -58,11 +71,8 @@ module Outpost
58
71
  # @param [String, #read] name The name to be given to a Outpost-based
59
72
  # class.
60
73
  def name(val=nil)
61
- if val
62
- @name = val
63
- else
64
- @name
65
- end
74
+ @name_template = val if val
75
+ @name_template
66
76
  end
67
77
  end
68
78
 
@@ -72,20 +82,57 @@ module Outpost
72
82
  # Returns a list of {Report} containing the last results of the last check.
73
83
  attr_reader :reports
74
84
 
85
+ # Returns all the registered scouts.
86
+ attr_reader :scouts
87
+
88
+ # Returns all the registered notifiers.
89
+ attr_reader :notifiers
90
+
91
+ # Reader/setter for the name of this scout
92
+ attr_accessor :name
93
+
75
94
  # New instance of a Outpost-based class.
76
95
  def initialize
77
- @reports = []
96
+ @reports = {}
78
97
  @last_status = nil
98
+ @scouts = Hash.new { |h, k| h[k] = {} }
99
+ @notifiers = {}
100
+ @name = self.class.name_template
101
+
102
+ # Register scouts
103
+ self.class.scout_templates.each do |template|
104
+ add_scout(template[:scout_description], &template[:block])
105
+ end
106
+
107
+ self.class.notifier_templates.each do |template|
108
+ add_notifier(template[:notifier], template[:options])
109
+ end
110
+ end
111
+
112
+ # @see Application#using
113
+ def add_scout(scout_description, &block)
114
+ config = ScoutConfig.new
115
+ config.instance_eval(&block)
116
+
117
+ scout_description.each do |scout, description|
118
+ @scouts[scout][:description] = description
119
+ @scouts[scout][:config] = config
120
+ end
121
+ end
122
+
123
+ # @see Application#notify
124
+ def add_notifier(notifier_name, options)
125
+ @notifiers[notifier_name] = options
79
126
  end
80
127
 
81
128
  # Execute all the scouts associated with an Outpost-based class and returns
82
129
  # either :up or :down, depending on the results.
83
130
  def run
84
- @reports = self.class.scouts.map do |scout, options|
85
- run_scout(scout, options)
131
+ scouts.map do |scout, options|
132
+ @reports[options[:description]] = run_scout(scout, options)
86
133
  end
87
134
 
88
- statuses = @reports.map { |r| r.status }
135
+ statuses = @reports.map { |_, r| r.status }
89
136
 
90
137
  @last_status = Report.summarize(statuses)
91
138
  end
@@ -93,7 +140,7 @@ module Outpost
93
140
  # Runs all notifications associated with an Outpost-based class.
94
141
  def notify
95
142
  if reports.any?
96
- self.class.notifiers.each do |notifier, options|
143
+ @notifiers.each do |notifier, options|
97
144
  # .dup is NOT reliable
98
145
  options_copy = Marshal.load(Marshal.dump(options))
99
146
  notifier.new(options_copy).notify(self)
@@ -106,24 +153,21 @@ module Outpost
106
153
  @last_status == :up
107
154
  end
108
155
 
156
+ # Returns true if the last status is :warning
157
+ def warning?
158
+ @last_status == :warning
159
+ end
160
+
109
161
  # Returns true if the last status is :down
110
162
  def down?
111
163
  @last_status == :down
112
164
  end
113
165
 
114
- # Returns the name of an Outpost-based class or the class name itself if
115
- # not set.
116
- #
117
- # @return [String] The name of the Outpost
118
- def name
119
- self.class.name || self.class.to_s
120
- end
121
-
122
166
  # Returns the messages of the latest service check.
123
167
  #
124
168
  # @return [Array<String>] An array containing all report messages.
125
169
  def messages
126
- reports.map { |r| r.to_s }
170
+ reports.map { |_, r| r.to_s }
127
171
  end
128
172
 
129
173
  private
@@ -135,7 +179,8 @@ module Outpost
135
179
  params = {
136
180
  :name => scout.name,
137
181
  :description => options[:description],
138
- :status => scout_instance.run
182
+ :status => scout_instance.run,
183
+ :data => scout_instance.report_data
139
184
  }
140
185
  Report.new(params)
141
186
  end
@@ -11,7 +11,11 @@ module Outpost
11
11
 
12
12
  # Method that will be used as an expectation to evaluate response code
13
13
  def evaluate_response_code(scout, response_code)
14
- scout.response_code == response_code.to_i
14
+ if response_code.is_a?(Array)
15
+ response_code.include?(scout.response_code)
16
+ else
17
+ scout.response_code == response_code.to_i
18
+ end
15
19
  end
16
20
  end
17
21
  end
@@ -16,15 +16,17 @@ module Outpost
16
16
  # @return [Symbol] the final status to be considered.
17
17
  def self.summarize(status_list)
18
18
  return :down if status_list.empty? || status_list.include?(:down)
19
+ return :warning if status_list.include?(:warning)
19
20
  return :up
20
21
  end
21
22
 
22
- attr_reader :name, :description, :status
23
+ attr_reader :name, :description, :status, :data
23
24
 
24
25
  def initialize(params)
25
26
  @name = params[:name]
26
27
  @description = params[:description]
27
28
  @status = params[:status]
29
+ @data = params[:data]
28
30
  end
29
31
 
30
32
  def to_s
@@ -18,6 +18,11 @@ module Outpost
18
18
  # scout.response_code == code
19
19
  # end
20
20
  #
21
+ # Scouts may also report specific data. This is useful to complement
22
+ # status reporting with the data received from the measurement. After the
23
+ # scout is run, each report data method will be executed and its calue will be
24
+ # added to the resulting report.
25
+ #
21
26
  # @example an example of a Scout that parses the HTTP response code
22
27
  # module Outpost
23
28
  # module Scouts
@@ -40,10 +45,40 @@ module Outpost
40
45
  # end
41
46
  # end
42
47
  #
48
+ # @example an example of a Scout that reports the duration of a HTTP request
49
+ # module Outpost
50
+ # module Scouts
51
+ # class TimedHttp < Outpost::Scout
52
+ # expect(:response_code) { |scout,code| scout.response_code == code }
53
+ #
54
+ # attr_reader :response_code, :response_time
55
+ # report_data :response_time
56
+ #
57
+ # def setup(options)
58
+ # @host = options[:host]
59
+ # @port = options[:port] || 80
60
+ # @path = options[:path] || '/'
61
+ # end
62
+ #
63
+ # def execute
64
+ # previous_time = Time.now
65
+ #
66
+ # response = Net::HTTP.get_response(@host, @path, @port)
67
+ # @response_code = response.code.to_i
68
+ #
69
+ # @response_time = Time.now - response_time
70
+ # end
71
+ # end
72
+ # end
73
+ # end
74
+ #
43
75
  # @abstract Subclasses must override {#setup} and {#execute} to be a valid
44
76
  # Scout
45
77
  class Scout
78
+ attr_reader :report_data
79
+
46
80
  class << self
81
+ attr_reader :reporting_data_methods
47
82
 
48
83
  # Returns the hash of expectations, where the key is the name of the
49
84
  # expectation and the value is the callable Object (object that responds
@@ -51,7 +86,7 @@ module Outpost
51
86
  #
52
87
  # @return [Hash<Symbol, Object>]
53
88
  def expectations
54
- @expectations ? @expectations.dup : []
89
+ @expectations ? @expectations.dup : {}
55
90
  end
56
91
 
57
92
  # Registers a new expectation into the Scout. If the callable does not
@@ -74,6 +109,16 @@ module Outpost
74
109
  raise ArgumentError, 'Object must respond to method #call to be a valid expectation.'
75
110
  end
76
111
  end
112
+
113
+ def reporting_data_methods
114
+ @reporting_data_methods ||= []
115
+ @reporting_data_methods
116
+ end
117
+
118
+ def report_data(*methods)
119
+ @reporting_data_methods ||= []
120
+ @reporting_data_methods += methods
121
+ end
77
122
  end
78
123
 
79
124
  # @param [String, #read] description A string containing a description of
@@ -83,6 +128,7 @@ module Outpost
83
128
  def initialize(description, config)
84
129
  @description = description
85
130
  @config = config
131
+ @report_data = {}
86
132
 
87
133
  setup(config.options)
88
134
  end
@@ -90,7 +136,7 @@ module Outpost
90
136
  # Executes the Scout and go through all the registered expectations to find
91
137
  # out all expectations that match and return the associated status.
92
138
  #
93
- # @return [Symbol] the current status of the Scout (:up, :down)
139
+ # @return [Symbol] the current status of the Scout (:up, :down, :warning)
94
140
  # @raise [NotImplementedError] raised when a configured expectation was not
95
141
  # registered in the Scout.
96
142
  def run
@@ -100,24 +146,37 @@ module Outpost
100
146
  # value.
101
147
  # Example: {:response_time => 200}
102
148
  #
103
- # status is the status (:up or :down, for example) that will be returned
149
+ # status is the status (:up, :down or :warning, for example) that will be returned
104
150
  # in case the expectation match current system status.
105
- @config.reports.each do |response_pair, status|
106
- response_pair.each do |expectation, value|
107
- if self.class.expectations[expectation].nil?
108
- message = "expectation '#{expectation}' wasn't implemented by #{self.class.name}"
109
- raise NotImplementedError, message
110
- end
151
+ if @config.reports.present?
152
+ @config.reports.each do |response_pair, status|
153
+ response_pair.each do |expectation, value|
154
+ if self.class.expectations[expectation].nil?
155
+ message = "expectation '#{expectation}' wasn't implemented by #{self.class.name}"
156
+ raise NotImplementedError, message
157
+ end
111
158
 
112
- if self.class.expectations[expectation].call(self, value)
113
- statuses << status
159
+ if self.class.expectations[expectation].call(self, value)
160
+ statuses << status
161
+ end
114
162
  end
115
163
  end
116
164
  end
117
165
 
166
+ gather_reporting_data
118
167
  Report.summarize(statuses)
119
168
  end
120
169
 
170
+ def gather_reporting_data
171
+ self.class.reporting_data_methods.each do |method|
172
+ if respond_to?(method)
173
+ @report_data[method.to_sym] = send(method)
174
+ else
175
+ raise ArgumentError, "Scout #{self.class.name} does not respond to ##{method} reporting data method"
176
+ end
177
+ end
178
+ end
179
+
121
180
  # Called when the scout object is being constructed. Arguments can be
122
181
  # everything the developer set in the creation of Outpost.
123
182
  #
@@ -12,11 +12,8 @@ module Outpost
12
12
  # @param [Object] args Any argument that will be passed to the Scout.
13
13
  # Rreturn [Object] The associated option
14
14
  def options(args=nil)
15
- if args.nil?
16
- @options
17
- else
18
- @options = args
19
- end
15
+ @options = args if args
16
+ @options
20
17
  end
21
18
 
22
19
  # Reads reporting as:
@@ -16,8 +16,10 @@ module Outpost
16
16
  class Http < Outpost::Scout
17
17
  extend Outpost::Expectations::ResponseCode
18
18
  extend Outpost::Expectations::ResponseBody
19
+ extend Outpost::Expectations::ResponseTime
19
20
 
20
- attr_reader :response_code, :response_body
21
+ attr_reader :response_code, :response_body, :response_time
22
+ report_data :response_code, :response_body, :response_time
21
23
 
22
24
  # Configure the scout with given options.
23
25
  # @param [Hash] Options to setup the scout
@@ -37,12 +39,14 @@ module Outpost
37
39
  # Runs the scout, connecting to the host and getting the response code,
38
40
  # body and time.
39
41
  def execute
42
+ previous_time = Time.now
40
43
  response = @http_class.get_response(@host, @path, @port)
41
44
 
45
+ @response_time = (Time.now - previous_time) * 1000 # Miliseconds
42
46
  @response_code = response.code.to_i
43
47
  @response_body = response.body
44
48
  rescue SocketError, Errno::ECONNREFUSED
45
- @response_code = @response_body = nil
49
+ @response_code = @response_body = @response_time = nil
46
50
  end
47
51
  end
48
52
  end
@@ -19,7 +19,7 @@ module Outpost
19
19
  class Ping < Outpost::Scout
20
20
  extend Outpost::Expectations::ResponseTime
21
21
  attr_reader :response_time
22
-
22
+ report_data :response_time
23
23
 
24
24
  # Configure the scout with given options.
25
25
  # @param [Hash] Options to setup the scout
@@ -1,5 +1,5 @@
1
1
  module Outpost
2
- PATCH = 0
2
+ PATCH = 1
3
3
  MINOR = 2
4
4
  MAJOR = 0
5
5
  VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}".freeze
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
12
12
 
13
13
  s.rubyforge_project = "outpost"
14
14
 
15
- s.files = `git ls-files`.split("\n")
16
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
- s.require_paths = ["lib"]
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
19
  end
@@ -6,10 +6,7 @@ describe "basic application integration test" do
6
6
  before(:each) do
7
7
  @server = Server.new
8
8
  @server.boot(TestApp)
9
-
10
- while !@server.responsive?
11
- sleep 0.1
12
- end
9
+ @server.wait_until_booted
13
10
  end
14
11
 
15
12
  class ExampleSuccess < Outpost::Application
@@ -19,6 +16,13 @@ describe "basic application integration test" do
19
16
  end
20
17
  end
21
18
 
19
+ class ExampleWarning < Outpost::Application
20
+ using Outpost::Scouts::Http => 'master http server' do
21
+ options :host => 'localhost', :port => 9595, :path => '/warning'
22
+ report :warning, :response_code => 402
23
+ end
24
+ end
25
+
22
26
  class ExampleFailure < Outpost::Application
23
27
  using Outpost::Scouts::Http => 'master http server' do
24
28
  options :host => 'localhost', :port => 9595, :path => '/fail'
@@ -40,10 +44,21 @@ describe "basic application integration test" do
40
44
  end
41
45
  end
42
46
 
47
+ class ExampleBodyWarning < Outpost::Application
48
+ using Outpost::Scouts::Http => 'master http server' do
49
+ options :host => 'localhost', :port => 9595, :path => '/warning'
50
+ report :warning, :response_body => {:equals => 'Omg need payment'}
51
+ end
52
+ end
53
+
43
54
  it "should report up when everything's ok" do
44
55
  assert_equal :up, ExampleSuccess.new.run
45
56
  end
46
57
 
58
+ it "should report warning when there's a warning" do
59
+ assert_equal :warning, ExampleWarning.new.run
60
+ end
61
+
47
62
  it "should report failure when something's wrong" do
48
63
  assert_equal :down, ExampleFailure.new.run
49
64
  end
@@ -52,6 +67,10 @@ describe "basic application integration test" do
52
67
  assert_equal :up, ExampleBodySuccess.new.run
53
68
  end
54
69
 
70
+ it "should report success when body have a warning" do
71
+ assert_equal :warning, ExampleBodyWarning.new.run
72
+ end
73
+
55
74
  it "should report failure when body is wrong" do
56
75
  assert_equal :down, ExampleBodyFailure.new.run
57
76
  end
@@ -6,10 +6,7 @@ describe "using more complex application integration test" do
6
6
  before(:each) do
7
7
  @server = Server.new
8
8
  @server.boot(TestApp)
9
-
10
- while !@server.responsive?
11
- sleep 0.1
12
- end
9
+ @server.wait_until_booted
13
10
  end
14
11
 
15
12
  class ExamplePingAndHttp < Outpost::Application
@@ -36,6 +33,30 @@ describe "using more complex application integration test" do
36
33
  end
37
34
  end
38
35
 
36
+ class ExampleOneWarningOnePassing < Outpost::Application
37
+ using Outpost::Scouts::Http => 'master http server' do
38
+ options :host => 'localhost', :port => 9595, :path => '/'
39
+ report :up, :response_body => {:match => /Up/}
40
+ end
41
+
42
+ using Outpost::Scouts::Http => 'master http server' do
43
+ options :host => 'localhost', :port => 9595, :path => '/warning'
44
+ report :warning, :response_code => 402
45
+ end
46
+ end
47
+
48
+ class ExampleOneWarningOneFailing < Outpost::Application
49
+ using Outpost::Scouts::Http => 'master http server' do
50
+ options :host => 'localhost', :port => 9595, :path => '/warning'
51
+ report :warning, :response_code => 402
52
+ end
53
+
54
+ using Outpost::Scouts::Ping => 'load balancer' do
55
+ options :host => 'localhost'
56
+ report :up, :response_time => {:less_than => 0}
57
+ end
58
+ end
59
+
39
60
  class ExampleAllFailing < Outpost::Application
40
61
  using Outpost::Scouts::Http => 'master http server' do
41
62
  options :host => 'localhost', :port => 9595, :path => '/fail'
@@ -52,8 +73,13 @@ describe "using more complex application integration test" do
52
73
  assert_equal :up, ExamplePingAndHttp.new.run
53
74
  end
54
75
 
76
+ it "should report warning when at least one scout reports warning" do
77
+ assert_equal :warning, ExampleOneWarningOnePassing.new.run
78
+ end
79
+
55
80
  it "should report down when at least one scout reports down" do
56
81
  assert_equal :down, ExampleOneFailingOnePassing.new.run
82
+ assert_equal :down, ExampleOneWarningOneFailing.new.run
57
83
  end
58
84
 
59
85
  it "should report down when all are down" do
@@ -0,0 +1,54 @@
1
+ require 'test_helper'
2
+
3
+ require 'outpost/scouts/http'
4
+ require 'outpost/notifiers/email'
5
+
6
+ describe "creating outpost apps without subclassing" do
7
+ before(:each) do
8
+ @server = Server.new
9
+ @server.boot(TestApp)
10
+ @server.wait_until_booted
11
+
12
+ Mail.defaults do
13
+ delivery_method :test
14
+ end
15
+ end
16
+
17
+ after(:each) do
18
+ Mail::TestMailer.deliveries = []
19
+ end
20
+
21
+ it "should report up when everything's ok" do
22
+ assert_equal :up, outpost.run
23
+ end
24
+
25
+ it "should notify up when everything's ok" do
26
+ outpost.add_notifier Outpost::Notifiers::Email, {
27
+ :from => 'outpost@example.com',
28
+ :to => 'sleep_deprived_admin@example.com',
29
+ :subject => 'System 1 status'
30
+ }
31
+
32
+ outpost.run
33
+ outpost.notify
34
+
35
+ refute_empty Mail::TestMailer.deliveries
36
+ end
37
+
38
+ it "should not notify when there are no notifiers" do
39
+ outpost.run
40
+
41
+ assert_empty Mail::TestMailer.deliveries
42
+ end
43
+
44
+ private
45
+
46
+ def outpost
47
+ @outpost ||= Outpost::Application.new.tap do |outpost|
48
+ outpost.add_scout Outpost::Scouts::Http => 'master http server' do
49
+ options :host => 'localhost', :port => 9595
50
+ report :up, :response_code => 200
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ require 'outpost/scouts'
4
+
5
+ describe "using only report data integration test" do
6
+ class RetrieveServerData < Outpost::Application
7
+ using Outpost::Scouts::Http => 'master http server' do
8
+ options :host => 'localhost', :port => 9595, :path => '/'
9
+ end
10
+
11
+ using Outpost::Scouts::Ping => :load_balancer do
12
+ options :host => 'localhost'
13
+ report :up, :response_time => {:less_than => 500}
14
+ end
15
+ end
16
+
17
+ before(:each) do
18
+ @server = Server.new
19
+ @server.boot(TestApp)
20
+ @server.wait_until_booted
21
+
22
+ @outpost = RetrieveServerData.new
23
+ @outpost.run
24
+ end
25
+
26
+ it "should build report for each scout" do
27
+ assert_equal 2, @outpost.reports.size
28
+ end
29
+
30
+ it "should build reports with data" do
31
+ http_report = @outpost.reports['master http server']
32
+ ping_report = @outpost.reports[:load_balancer]
33
+
34
+ report_data = http_report.data
35
+
36
+ assert report_data.delete(:response_time) < 500
37
+ assert_equal({:response_code => 200,
38
+ :response_body => 'Up and running!'}, http_report.data)
39
+ end
40
+ end
@@ -5,8 +5,10 @@ describe Outpost::Application do
5
5
  class << self
6
6
  attr_accessor :status
7
7
  end
8
+
8
9
  def initialize(*args); end
9
10
  def run ; self.class.status ; end
11
+ def report_data; ; {} ; end
10
12
  end
11
13
 
12
14
  class NotifierMock
@@ -32,7 +34,7 @@ describe Outpost::Application do
32
34
  end
33
35
 
34
36
  before(:each) do
35
- @scouts = ExampleOne.scouts
37
+ @scouts = ExampleOne.new.scouts
36
38
  end
37
39
 
38
40
  it "should create correct scout description" do
@@ -47,10 +49,37 @@ describe Outpost::Application do
47
49
  end
48
50
 
49
51
  it "should create notifiers configuration" do
50
- notifiers = ExampleOne.notifiers
52
+ notifiers = ExampleOne.new.notifiers
51
53
  assert_equal({NotifierMock => {:email => 'mail@example.com'}}, notifiers)
52
54
  end
53
55
 
56
+ describe "#add_scout" do
57
+ before(:each) do
58
+ ScoutMock.status = :up
59
+ end
60
+
61
+ it "should be able to create scouts programatically" do
62
+ outpost = create_outpost
63
+
64
+ assert_equal :up, outpost.run
65
+ end
66
+ end
67
+
68
+ describe "#add_notifier" do
69
+ before(:each) do
70
+ ScoutMock.status = :up
71
+ end
72
+
73
+ it "should be able to create notifications programatically" do
74
+ outpost = create_outpost
75
+ outpost.run
76
+ outpost.notify
77
+
78
+ assert_equal "ScoutMock: 'master http server' is reporting up.",
79
+ NotifierMock.last_messages.first
80
+ end
81
+ end
82
+
54
83
  describe "#run" do
55
84
  it "should return up when scouts return up" do
56
85
  ScoutMock.status = :up
@@ -84,6 +113,7 @@ describe Outpost::Application do
84
113
 
85
114
  refute NotifierMock.last_messages
86
115
  end
116
+
87
117
  end
88
118
 
89
119
  describe "#up?" do
@@ -106,6 +136,26 @@ describe Outpost::Application do
106
136
  end
107
137
  end
108
138
 
139
+ describe "#warning?" do
140
+ before(:each) do
141
+ @outpost = ExampleOne.new
142
+ end
143
+
144
+ it "should return true when last status is warning" do
145
+ ScoutMock.status = :warning
146
+ @outpost.run
147
+
148
+ assert @outpost.warning?
149
+ end
150
+
151
+ it "should return false when last status isn't warning" do
152
+ ScoutMock.status = :up
153
+ @outpost.run
154
+
155
+ refute @outpost.warning?
156
+ end
157
+ end
158
+
109
159
  describe "#down?" do
110
160
  before(:each) do
111
161
  @outpost = ExampleOne.new
@@ -154,4 +204,20 @@ describe Outpost::Application do
154
204
  @outpost.messages.first
155
205
  end
156
206
  end
207
+
208
+ describe "#reports" do
209
+
210
+ end
211
+
212
+ private
213
+
214
+ def create_outpost
215
+ Outpost::Application.new.tap do |outpost|
216
+ outpost.add_scout ScoutMock => 'master http server' do
217
+ report :up, :response_code => 200
218
+ end
219
+
220
+ outpost.add_notifier NotifierMock, :email => 'mail@example.com'
221
+ end
222
+ end
157
223
  end
@@ -36,6 +36,14 @@ describe Outpost::Expectations::ResponseCode do
36
36
  SubjectCode.evaluation_method
37
37
  end
38
38
 
39
+ it "should return true when response code is included in the list" do
40
+ assert SubjectCode.evaluate_response_code(scout_stub, [200, 301])
41
+ end
42
+
43
+ it "should refute when response code is not included in the list" do
44
+ refute SubjectCode.evaluate_response_code(scout_stub, [500, 503])
45
+ end
46
+
39
47
  private
40
48
  def scout_stub
41
49
  build_stub(:response_code => 200)
@@ -16,4 +16,16 @@ describe Outpost::Report do
16
16
  it "should report down when there are no statuses" do
17
17
  assert_equal :down, Outpost::Report.summarize([])
18
18
  end
19
+
20
+ it "should report warning when all are warning" do
21
+ assert_equal :warning, Outpost::Report.summarize([:warning, :warning])
22
+ end
23
+
24
+ it "should report warning when mixed up and warning" do
25
+ assert_equal :warning, Outpost::Report.summarize([:warning, :up, :up])
26
+ end
27
+
28
+ it "should report down when mixed down and warning" do
29
+ assert_equal :down, Outpost::Report.summarize([:warning, :down, :up])
30
+ end
19
31
  end
@@ -11,6 +11,39 @@ describe Outpost::Scout do
11
11
  def execute(*args); end
12
12
  end
13
13
 
14
+ class ReportingScoutExample < Outpost::Scout
15
+ attr_accessor :response
16
+ report_data :translated_response_code
17
+ expect :response, lambda {|scout, status| scout.response == status }
18
+
19
+ def setup(*args); end
20
+ def execute(*args); end
21
+
22
+ def translated_response_code
23
+ "Page Not Found"
24
+ end
25
+ end
26
+
27
+ class BrokenScoutExample < Outpost::Scout
28
+ attr_accessor :response
29
+ report_data :reportive
30
+ expect :response, lambda {|scout, status| scout.response == status }
31
+
32
+ def setup(*args); end
33
+ def execute(*args); end
34
+ end
35
+
36
+ class MultipleReportsScoutExample < Outpost::Scout
37
+ attr_accessor :response
38
+ expect :response, lambda {|scout, status| scout.response == status }
39
+ def response_time; 10; end
40
+ def response_code; 200; end
41
+ def setup(*args); end
42
+ def execute(*args); end
43
+
44
+ report_data :response_time, :response_code
45
+ end
46
+
14
47
  it "should report up when status match" do
15
48
  scout = ScoutExample.new("a scout", config_mock)
16
49
  scout.response = true
@@ -75,6 +108,36 @@ describe Outpost::Scout do
75
108
  end
76
109
  end
77
110
 
111
+ it "should complain when a scout does not respond to the method supplied" do
112
+ assert_raises(ArgumentError, 'Scout BrokenScoutExample does not respond to #reportive reporting method') do
113
+
114
+ scout = BrokenScoutExample.new("a scout", config_mock)
115
+ scout.run
116
+ end
117
+ end
118
+
119
+ it "should set the report data hash as empty when scout has not been run yet" do
120
+ scout = ReportingScoutExample.new("a scout", config_mock)
121
+ assert_equal({}, scout.report_data)
122
+ end
123
+
124
+ it "should fill the report data hash with data collected after being run" do
125
+ scout = ReportingScoutExample.new("a scout", config_mock)
126
+ scout.run
127
+
128
+ assert_equal({:translated_response_code => "Page Not Found"}, scout.report_data)
129
+ end
130
+
131
+ it "should accept multiple method names" do
132
+ scout = MultipleReportsScoutExample.new("a scout", config_mock)
133
+ scout.run
134
+
135
+ assert_equal({
136
+ :response_time => 10,
137
+ :response_code => 200
138
+ }, scout.report_data)
139
+ end
140
+
78
141
  private
79
142
  def config_mock
80
143
  reports = {
@@ -24,6 +24,14 @@ describe Outpost::Scouts::Http do
24
24
  assert_equal 'Body', @subject.response_body
25
25
  end
26
26
 
27
+ it "should set the reporting data accordingly" do
28
+ NetHttpStub.response { response_stub('200', 'Body') }
29
+ @subject.run
30
+
31
+ assert_equal 200 , @subject.report_data[:response_code]
32
+ assert_equal 'Body', @subject.report_data[:response_body]
33
+ end
34
+
27
35
  it "should set response code and body as nil when connection refused" do
28
36
  NetHttpStub.response { raise Errno::ECONNREFUSED }
29
37
  @subject.execute
@@ -27,6 +27,14 @@ describe Outpost::Scouts::Ping do
27
27
  refute subject.response_time
28
28
  end
29
29
 
30
+ it "should report the response time" do
31
+ config = config_stub(:pinger => PingStub.new(true, 0.225))
32
+ subject = Outpost::Scouts::Ping.new "test", config
33
+ subject.run
34
+
35
+ assert_equal 225, subject.report_data[:response_time]
36
+ end
37
+
30
38
  private
31
39
 
32
40
  def config_stub(options={})
@@ -2,6 +2,12 @@ require 'rack/handler/thin'
2
2
  require 'net/http'
3
3
 
4
4
  class Server
5
+ def wait_until_booted
6
+ while !responsive?
7
+ sleep 0.1
8
+ end
9
+ end
10
+
5
11
  # Got it from Capybara, but simplified it a bit.
6
12
  # lib/capybara/server.rb
7
13
  def responsive?
@@ -8,4 +8,8 @@ class TestApp < Sinatra::Base
8
8
  get '/fail' do
9
9
  [500, 'Omg fail']
10
10
  end
11
+
12
+ get '/warning' do
13
+ [402, 'Omg need payment']
14
+ end
11
15
  end
metadata CHANGED
@@ -1,33 +1,23 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: outpost
3
- version: !ruby/object:Gem::Version
4
- hash: 23
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 2
9
- - 0
10
- version: 0.2.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Vinicius Baggio Fuentes
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-02-12 00:00:00 -02:00
12
+ date: 2011-06-26 00:00:00.000000000 -03:00
19
13
  default_executable:
20
14
  dependencies: []
21
-
22
15
  description: Simple service monitoring with a clean DSL for configuration.
23
16
  email: vinibaggio@gmail.com
24
17
  executables: []
25
-
26
18
  extensions: []
27
-
28
19
  extra_rdoc_files: []
29
-
30
- files:
20
+ files:
31
21
  - .gitignore
32
22
  - CHANGELOG.rdoc
33
23
  - Gemfile
@@ -55,8 +45,10 @@ files:
55
45
  - outpost.gemspec
56
46
  - test/integration/basic_application_test.rb
57
47
  - test/integration/more_complex_test.rb
48
+ - test/integration/no_subclassing_test.rb
58
49
  - test/integration/notifiers_test.rb
59
- - test/outpost/dsl_test.rb
50
+ - test/integration/reporting_test.rb
51
+ - test/outpost/application_test.rb
60
52
  - test/outpost/expectations/response_body_test.rb
61
53
  - test/outpost/expectations/response_code_test.rb
62
54
  - test/outpost/expectations/response_time_test.rb
@@ -75,42 +67,35 @@ files:
75
67
  has_rdoc: true
76
68
  homepage: http://www.github.com/vinibaggio/outpost
77
69
  licenses: []
78
-
79
70
  post_install_message:
80
71
  rdoc_options: []
81
-
82
- require_paths:
72
+ require_paths:
83
73
  - lib
84
- required_ruby_version: !ruby/object:Gem::Requirement
74
+ required_ruby_version: !ruby/object:Gem::Requirement
85
75
  none: false
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- hash: 3
90
- segments:
91
- - 0
92
- version: "0"
93
- required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
81
  none: false
95
- requirements:
96
- - - ">="
97
- - !ruby/object:Gem::Version
98
- hash: 3
99
- segments:
100
- - 0
101
- version: "0"
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
102
86
  requirements: []
103
-
104
87
  rubyforge_project: outpost
105
- rubygems_version: 1.3.7
88
+ rubygems_version: 1.6.2
106
89
  signing_key:
107
90
  specification_version: 3
108
91
  summary: Simple service monitoring with a clean DSL for configuration.
109
- test_files:
92
+ test_files:
110
93
  - test/integration/basic_application_test.rb
111
94
  - test/integration/more_complex_test.rb
95
+ - test/integration/no_subclassing_test.rb
112
96
  - test/integration/notifiers_test.rb
113
- - test/outpost/dsl_test.rb
97
+ - test/integration/reporting_test.rb
98
+ - test/outpost/application_test.rb
114
99
  - test/outpost/expectations/response_body_test.rb
115
100
  - test/outpost/expectations/response_code_test.rb
116
101
  - test/outpost/expectations/response_time_test.rb