outpost 0.1.0

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.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem 'net-ping', :require => false
4
+
5
+ group :test do
6
+ gem 'thin'
7
+ gem 'rack'
8
+ gem 'sinatra'
9
+ gem 'ruby-debug19'
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ archive-tar-minitar (0.5.2)
5
+ columnize (0.3.2)
6
+ daemons (1.1.0)
7
+ eventmachine (0.12.10)
8
+ linecache19 (0.5.11)
9
+ ruby_core_source (>= 0.1.4)
10
+ net-ping (1.3.7)
11
+ rack (1.2.1)
12
+ ruby-debug-base19 (0.11.24)
13
+ columnize (>= 0.3.1)
14
+ linecache19 (>= 0.5.11)
15
+ ruby_core_source (>= 0.1.4)
16
+ ruby-debug19 (0.11.6)
17
+ columnize (>= 0.3.1)
18
+ linecache19 (>= 0.5.11)
19
+ ruby-debug-base19 (>= 0.11.19)
20
+ ruby_core_source (0.1.4)
21
+ archive-tar-minitar (>= 0.5.2)
22
+ sinatra (1.1.2)
23
+ rack (~> 1.1)
24
+ tilt (~> 1.2)
25
+ thin (1.2.7)
26
+ daemons (>= 1.0.9)
27
+ eventmachine (>= 0.12.6)
28
+ rack (>= 1.0.0)
29
+ tilt (1.2.2)
30
+
31
+ PLATFORMS
32
+ ruby
33
+
34
+ DEPENDENCIES
35
+ net-ping
36
+ rack
37
+ ruby-debug19
38
+ sinatra
39
+ thin
data/README.markdown ADDED
@@ -0,0 +1,159 @@
1
+ # Outpost
2
+
3
+ Outpost is under development and is a project for the RMU: Ruby Mendicant
4
+ University.
5
+
6
+ ## Features
7
+
8
+ Outpost is a tool to monitor the state of your service (not server). What does it mean?
9
+
10
+ It means:
11
+
12
+ * it can monitor the state of a server, such as MySQL;
13
+ * it can monitor some business rule to see if everything is running accordingly (such as cron jobs)
14
+ * it can monitor several servers
15
+ * it can monitor whatever you can code with Ruby
16
+
17
+ It will connect to the related machines (it won't have any proxies/agents running on the servers to
18
+ report data) and collect the data. The idea is to be completely uncoupled with the systems.
19
+ It should report a status per declared system.
20
+
21
+ The idea is to make a reliable framework for the Ruby developer to create his own monitoring rules.
22
+ So, summing it all up, Nagios in Ruby, much cooler!
23
+
24
+ ## Installing
25
+
26
+ gem install outpost
27
+
28
+ ## Starting
29
+
30
+ To create your Outposts, you must require 'outpost'. You also need to include
31
+ 'outpost/scouts' if you want to use the supplied scouts. Example:
32
+
33
+ require 'outpost'
34
+ require 'outpost/scouts'
35
+
36
+ class Bla < Outpost::DSL
37
+ using Outpost::Scouts::Http => "web page" do
38
+ options :host => 'localhost', :port => 3000
39
+ report :up, :response_code => 200
40
+ end
41
+ end
42
+
43
+ a = Bla.new
44
+ a.run
45
+ p a.messages # => ["Outpost::Scouts::Http: 'web page' is reporting up."]
46
+
47
+
48
+ ## How it works
49
+
50
+ Consider the following example:
51
+
52
+ require 'outpost'
53
+ require 'outpost/scouts'
54
+
55
+ class HttpOutpostExample < Outpost::DSL
56
+ using Outpost::Scouts::Http => "web page" do
57
+ options :host => 'localhost', :port => 3000
58
+ report :up, :response_code => 200
59
+ report :down, :response_body => {:match => /Ops/}
60
+ end
61
+ end
62
+ outpost = HttpOutpostExample.new
63
+ outpost.run # => :down
64
+
65
+ In this simple example, an Outpost was created to monitor a web server running
66
+ on localhost at port 3000. Every time #run is called, the outpost will
67
+ run associated rules (in this example, check if the HTTP response code is 200
68
+ and report "up" if it does and also check if the response body matches /Ops/,
69
+ reporting "down" in that case).
70
+
71
+ ## Outpost
72
+
73
+ Outpost is the description of the system and provides a DSL to do it.
74
+ Check "How it works" section for an example, or check the [integration tests](https://github.com/vinibaggio/outpost/blob/master/test/integration/basic_dsl_test.rb)
75
+ for more.
76
+
77
+ ## Scout
78
+
79
+ Scout are pure Ruby classes that will test your server. For instance, check the
80
+ Outpost::Scouts::Http example below:
81
+
82
+ module Outpost
83
+ module Scouts
84
+ class Http < Outpost::Scout
85
+ extend Outpost::Expectations::ResponseCode
86
+ extend Outpost::Expectations::ResponseBody
87
+
88
+ attr_reader :response_code, :response_body
89
+
90
+ def setup(options)
91
+ @host = options[:host]
92
+ @port = options[:port] || 80
93
+ @path = options[:path] || '/'
94
+ end
95
+
96
+ def execute
97
+ response = Net::HTTP.get_response(@host, @path, @port)
98
+ @response_code = response.code.to_i
99
+ @response_body = response.body
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ It must implement the #setup and #execute methods. The magic lies in the #execute
106
+ method, where you can implement any kind of logic to test whether your system is up
107
+ or not. You may also include expectations in order to process the output of your system.
108
+ For more information about expectations, check the section below.
109
+
110
+ ## Expectations
111
+
112
+ Consider the following code snippet, taken from previous examples:
113
+
114
+ report :up, :response_code => 200
115
+ report :down, :response_body => {:match => /Ops/}
116
+
117
+ In the example above, :response\_code and :response\_body are expectations, responsible
118
+ to get Scout's output and evaluate it, in order to determine a status.
119
+
120
+ They must be registered into each Scout that wish to support different types
121
+ of expectations. You can supply a block or an object that respond to #call
122
+ and return true if any of the rules match. It will receive an instance
123
+ of the scout (so you can query current system state) as the first parameter
124
+ and the state defined in the #report method as the second.
125
+
126
+ So you can easily create your own expectation. Let's recreate the :response\_code in
127
+ Outpost::Scouts::Http:
128
+
129
+ module Outpost
130
+ module Scouts
131
+ class Http < Outpost::Scout
132
+ expect(:response_code) { |scout,code| scout.response_code == code }
133
+
134
+ attr_reader :response_code
135
+
136
+ def setup(options)
137
+ @host = options[:host]
138
+ @port = options[:port] || 80
139
+ @path = options[:path] || '/'
140
+ end
141
+
142
+ def execute
143
+ response = Net::HTTP.get_response(@host, @path, @port)
144
+ @response_code = response.code.to_i
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ You can also check the supplied expectations in the source of the project to have
151
+ an idea on how to implement more complex rules.
152
+
153
+ ## TODO
154
+
155
+ There's a lot to be done yet. For example, SSH support, :warning status, etc.
156
+
157
+ ## License
158
+
159
+ MIT.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rake/testtask'
2
+
3
+ desc 'Default: run tests'
4
+ task :default => :test
5
+
6
+ desc 'Run tests.'
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << 'lib'
9
+ t.libs << 'test'
10
+ t.pattern = 'test/**/*_test.rb'
11
+ t.verbose = true
12
+ end
data/lib/outpost.rb ADDED
@@ -0,0 +1,8 @@
1
+
2
+ # Outpost definition
3
+ require 'outpost/dsl'
4
+ require 'outpost/report'
5
+
6
+ # Scout definition
7
+ require 'outpost/scout_config'
8
+ require 'outpost/scout'
@@ -0,0 +1,54 @@
1
+ module Outpost
2
+ class DSL
3
+ class << self
4
+ attr_reader :scouts
5
+
6
+ def using(scouts, &block)
7
+ @scouts ||= Hash.new { |h, k| h[k] = {} }
8
+
9
+ config = ScoutConfig.new
10
+ config.instance_eval(&block)
11
+
12
+ scouts.each do |scout, description|
13
+ @scouts[scout][:description] = description
14
+ @scouts[scout][:config] = config
15
+ end
16
+ end
17
+ end
18
+
19
+ attr_reader :last_status, :reports
20
+
21
+ def run
22
+ @reports = self.class.scouts.map do |scout, options|
23
+ run_scout(scout, options)
24
+ end
25
+
26
+ statuses = @reports.map { |r| r.status }
27
+
28
+ @last_status = Report.summarize(statuses)
29
+ end
30
+
31
+ def up?
32
+ @last_status == :up
33
+ end
34
+
35
+ def down?
36
+ @last_status == :down
37
+ end
38
+
39
+ def messages
40
+ reports.map { |r| r.to_s }
41
+ end
42
+
43
+ def run_scout(scout, options)
44
+ scout_instance = scout.new(options[:description], options[:config])
45
+
46
+ params = {
47
+ :name => scout.name,
48
+ :description => options[:description],
49
+ :status => scout_instance.run
50
+ }
51
+ Report.new(params)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ require 'outpost/expectations/response_code'
2
+ require 'outpost/expectations/response_body'
3
+ require 'outpost/expectations/response_time'
@@ -0,0 +1,22 @@
1
+ module Outpost
2
+ module Expectations
3
+ module ResponseBody
4
+ RESPONSE_BODY_MAPPING = {
5
+ :match => "=~",
6
+ :not_match => "!~",
7
+ :equals => "==",
8
+ :differs => "!="
9
+ }.freeze
10
+
11
+ def self.extended(base)
12
+ base.expect :response_body, base.method(:evaluate_response_body)
13
+ end
14
+
15
+ def evaluate_response_body(scout, rules)
16
+ rules.all? do |rule,comparison|
17
+ scout.response_body.send(RESPONSE_BODY_MAPPING[rule], comparison)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Outpost
2
+ module Expectations
3
+ module ResponseCode
4
+ def self.extended(base)
5
+ base.expect :response_code, base.method(:evaluate_response_code)
6
+ end
7
+
8
+ def evaluate_response_code(scout, response_code)
9
+ scout.response_code == response_code.to_i
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Outpost
2
+ module Expectations
3
+ module ResponseTime
4
+ RESPONSE_TIME_MAPPING = {
5
+ :less_than => "<",
6
+ :more_than => ">",
7
+ }.freeze
8
+
9
+ def self.extended(base)
10
+ base.expect :response_time, base.method(:evaluate_response_time)
11
+ end
12
+
13
+ def evaluate_response_time(scout, rules)
14
+ rules.all? do |rule,comparison|
15
+ scout.response_time.send(RESPONSE_TIME_MAPPING[rule], comparison)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module Outpost
2
+ class Report
3
+ # summarizes the list of statuses in a single status only.
4
+ # The logic is rather simple - it will return the lowest status
5
+ # present in the list.
6
+ #
7
+ # Examples:
8
+ #
9
+ # if passed [:up, :up, :up], will result on :up
10
+ # if passed [:up, :down, :up], will result on :down
11
+ def self.summarize(status_list)
12
+ return :down if status_list.empty? || status_list.include?(:down)
13
+ return :up
14
+ end
15
+
16
+ attr_reader :name, :description, :status
17
+
18
+ def initialize(params)
19
+ @name = params[:name]
20
+ @description = params[:description]
21
+ @status = params[:status]
22
+ end
23
+
24
+ def to_s
25
+ "#{name}: '#{description}' is reporting #{status}."
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ module Outpost
2
+ class Scout
3
+ class << self
4
+
5
+ def expectations
6
+ @expectations ? @expectations.dup : []
7
+ end
8
+
9
+ def expect(expectation, callable=nil, &callable_block)
10
+ callable ||= callable_block
11
+
12
+ if callable.respond_to?(:call)
13
+ @expectations ||= {}
14
+ @expectations[expectation] = callable
15
+ else
16
+ raise ArgumentError, 'Object must respond to method #call to be a valid expectation.'
17
+ end
18
+ end
19
+ end
20
+
21
+ def initialize(description, config)
22
+ @description = description
23
+ @config = config
24
+
25
+ setup(config.options)
26
+ end
27
+
28
+ def run
29
+ statuses = []
30
+ execute
31
+ @config.reports.each do |response_pair, status|
32
+ response_pair.each do |expectation, value|
33
+ if self.class.expectations[expectation].nil?
34
+ message = "expectation '#{expectation}' wasn't implemented by #{self.class.name}"
35
+ raise NotImplementedError, message
36
+ end
37
+
38
+ if self.class.expectations[expectation].call(self, value)
39
+ statuses << status
40
+ end
41
+ end
42
+ end
43
+
44
+ Report.summarize(statuses)
45
+ end
46
+
47
+ def setup(*args)
48
+ raise NotImplementedError, 'You must implement the setup method for Scout to work correctly.'
49
+ end
50
+
51
+ def execute(*args)
52
+ raise NotImplementedError, 'You must implement the execute method for Scout to work correctly.'
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ module Outpost
2
+ class ScoutConfig
3
+ attr_reader :options, :reports
4
+
5
+ # Reads/writes any options. It will passed
6
+ # down to the scout.
7
+ def options(args=nil)
8
+ if args.nil?
9
+ @options
10
+ else
11
+ @options = args
12
+ end
13
+ end
14
+
15
+ # Reads reporting as:
16
+ # report :up, :response_code => 200
17
+ #
18
+ # It reads much better in the DSL, but doesn't make
19
+ # much sense in terms of code, so it is changed to
20
+ # an inverted approach, so:
21
+ # status = 200
22
+ # params = {:response_code => 200}
23
+ #
24
+ # gets stored as:
25
+ # {:response_code => 200} = up
26
+ def report(status, params)
27
+ @reports ||= {}
28
+ @reports[params] = status
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,2 @@
1
+ require 'outpost/scouts/http'
2
+ require 'outpost/scouts/ping'
@@ -0,0 +1,28 @@
1
+ require 'net/http'
2
+ require 'outpost'
3
+
4
+ require 'outpost/expectations'
5
+
6
+ module Outpost
7
+ module Scouts
8
+ class Http < Outpost::Scout
9
+ extend Outpost::Expectations::ResponseCode
10
+ extend Outpost::Expectations::ResponseBody
11
+
12
+ attr_reader :response_code, :response_body
13
+
14
+ def setup(options)
15
+ @host = options[:host]
16
+ @port = options[:port] || 80
17
+ @path = options[:path] || '/'
18
+ end
19
+
20
+ def execute
21
+ # FIXME Apply Dependency Injection Principle here
22
+ response = Net::HTTP.get_response(@host, @path, @port)
23
+ @response_code = response.code.to_i
24
+ @response_body = response.body
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ require 'net/ping/external'
2
+
3
+ module Outpost
4
+ module Scouts
5
+ class Ping < Outpost::Scout
6
+ extend Outpost::Expectations::ResponseTime
7
+ attr_reader :response_time
8
+
9
+ def setup(options)
10
+ @host = options[:host]
11
+ end
12
+
13
+ def execute
14
+ # FIXME Apply Dependency Injection Principle here
15
+ pinger = Net::Ping::External.new
16
+ if pinger.ping(@host)
17
+ # Miliseconds
18
+ @response_time = pinger.duration * 100
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Outpost
2
+ VERSION = "0.1.0".freeze
3
+ end
data/outpost.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "outpost/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "outpost"
6
+ s.version = Outpost::VERSION.dup
7
+ s.description = "Simple service monitoring with a clean DSL for configuration."
8
+ s.summary = "Simple service monitoring with a clean DSL for configuration."
9
+ s.author = "Vinicius Baggio Fuentes"
10
+ s.email = "vinibaggio@gmail.com"
11
+ s.homepage = "http://www.github.com/vinibaggio/outpost"
12
+
13
+ s.rubyforge_project = "outpost"
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"]
19
+
20
+ s.add_dependency("net-ping", "~> 1.3.7")
21
+ end
@@ -0,0 +1,58 @@
1
+ require 'test_helper'
2
+
3
+ require 'outpost/scouts/http'
4
+
5
+ describe "basic DSL integration test" do
6
+ before(:each) do
7
+ @server = Server.new
8
+ @server.boot(TestApp)
9
+
10
+ while !@server.responsive?
11
+ sleep 0.1
12
+ end
13
+ end
14
+
15
+ class ExampleSuccess < Outpost::DSL
16
+ using Outpost::Scouts::Http => 'master http server' do
17
+ options :host => 'localhost', :port => 9595
18
+ report :up, :response_code => 200
19
+ end
20
+ end
21
+
22
+ class ExampleFailure < Outpost::DSL
23
+ using Outpost::Scouts::Http => 'master http server' do
24
+ options :host => 'localhost', :port => 9595, :path => '/fail'
25
+ report :up, :response_code => 200
26
+ end
27
+ end
28
+
29
+ class ExampleBodyFailure < Outpost::DSL
30
+ using Outpost::Scouts::Http => 'master http server' do
31
+ options :host => 'localhost', :port => 9595, :path => '/fail'
32
+ report :down, :response_body => {:equals => 'Omg fail'}
33
+ end
34
+ end
35
+
36
+ class ExampleBodySuccess < Outpost::DSL
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
+ end
42
+
43
+ it "should report up when everything's ok" do
44
+ assert_equal :up, ExampleSuccess.new.run
45
+ end
46
+
47
+ it "should report failure when something's wrong" do
48
+ assert_equal :down, ExampleFailure.new.run
49
+ end
50
+
51
+ it "should report success when body is okay" do
52
+ assert_equal :up, ExampleBodySuccess.new.run
53
+ end
54
+
55
+ it "should report failure when body is wrong" do
56
+ assert_equal :down, ExampleBodyFailure.new.run
57
+ end
58
+ end
@@ -0,0 +1,64 @@
1
+ require 'test_helper'
2
+
3
+ require 'outpost/scouts'
4
+
5
+ describe "using more complex DSL integration test" do
6
+ class ExamplePingAndHttp < Outpost::DSL
7
+ using Outpost::Scouts::Http => 'master http server' do
8
+ options :host => 'localhost', :port => 9595, :path => '/'
9
+ report :up, :response_body => {:match => /Up/}
10
+ end
11
+
12
+ using Outpost::Scouts::Ping => 'load balancer' do
13
+ options :host => 'localhost'
14
+ report :up, :response_time => {:less_than => 500}
15
+ end
16
+ end
17
+
18
+ class ExampleOneFailingOnePassing < Outpost::DSL
19
+ using Outpost::Scouts::Http => 'master http server' do
20
+ options :host => 'localhost', :port => 9595, :path => '/'
21
+ report :up, :response_body => {:match => /Up/}
22
+ end
23
+
24
+ using Outpost::Scouts::Ping => 'load balancer' do
25
+ options :host => 'localhost'
26
+ report :up, :response_time => {:less_than => 0}
27
+ end
28
+ end
29
+
30
+ class ExampleAllFailing < Outpost::DSL
31
+ using Outpost::Scouts::Http => 'master http server' do
32
+ options :host => 'localhost', :port => 9595, :path => '/fail'
33
+ report :up, :response_body => {:match => /Up/}
34
+ end
35
+
36
+ using Outpost::Scouts::Ping => 'load balancer' do
37
+ options :host => 'localhost'
38
+ report :up, :response_time => {:less_than => -1}
39
+ end
40
+ end
41
+
42
+ it "should report up when everything's ok" do
43
+ assert_equal :up, ExamplePingAndHttp.new.run
44
+ end
45
+
46
+ it "should report down when at least one scout reports down" do
47
+ assert_equal :down, ExampleOneFailingOnePassing.new.run
48
+ end
49
+
50
+ it "should report down when all are down" do
51
+ assert_equal :down, ExampleAllFailing.new.run
52
+ end
53
+
54
+ it "should build error message" do
55
+ outpost = ExampleAllFailing.new
56
+ outpost.run
57
+
58
+ assert_equal "Outpost::Scouts::Http: 'master http server' is reporting down.",
59
+ outpost.messages.first
60
+
61
+ assert_equal "Outpost::Scouts::Ping: 'load balancer' is reporting down.",
62
+ outpost.messages.last
63
+ end
64
+ end
@@ -0,0 +1,86 @@
1
+ require 'test_helper'
2
+
3
+ describe Outpost::DSL do
4
+ class ScoutMock
5
+ class << self
6
+ attr_accessor :status
7
+ end
8
+ def run; self.class.status; end
9
+ end
10
+
11
+ class ExampleOne < Outpost::DSL
12
+ using ScoutMock => 'master http server' do
13
+ options :host => 'localhost'
14
+ report :up, :response_code => 200
15
+ end
16
+ end
17
+
18
+ before(:each) do
19
+ @scouts = ExampleOne.scouts
20
+ end
21
+
22
+ it "should create correct scout description" do
23
+ assert_equal(ScoutMock, @scouts.keys.first)
24
+ assert_equal('master http server', @scouts[ScoutMock][:description])
25
+ end
26
+
27
+ it "should create correct scout config" do
28
+ config = @scouts[ScoutMock][:config]
29
+ assert_equal({:host => 'localhost'}, config.options)
30
+ assert_equal({{:response_code => 200} => :up}, config.reports)
31
+ end
32
+
33
+ describe "#up?" do
34
+ before(:each) do
35
+ @outpost = ExampleOne.new
36
+ end
37
+
38
+ it "should return true when last status is up" do
39
+ ScoutMock.status = :up
40
+ @outpost.run
41
+
42
+ assert @outpost.up?
43
+ end
44
+
45
+ it "should return false when last status isn't up" do
46
+ ScoutMock.status = :down
47
+ @outpost.run
48
+
49
+ refute @outpost.up?
50
+ end
51
+ end
52
+
53
+ describe "#down?" do
54
+ before(:each) do
55
+ @outpost = ExampleOne.new
56
+ end
57
+
58
+ it "should return true when last status is down" do
59
+ ScoutMock.status = :down
60
+ @outpost.run
61
+
62
+ assert @outpost.down?
63
+ end
64
+
65
+ it "should return false when last status isn't down" do
66
+ ScoutMock.status = :up
67
+ @outpost.run
68
+
69
+ refute @outpost.down?
70
+ end
71
+ end
72
+
73
+ describe "#messages" do
74
+ before(:each) do
75
+ @outpost = ExampleOne.new
76
+ end
77
+
78
+ it "should return true when last status is up" do
79
+ ScoutMock.status = :up
80
+ @outpost.run
81
+
82
+ assert_equal "ScoutMock: 'master http server' is reporting up.",
83
+ @outpost.messages.first
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,89 @@
1
+ require 'test_helper'
2
+
3
+ describe Outpost::Expectations::ResponseBody do
4
+ class SubjectBody
5
+ class << self
6
+ attr_reader :expectation, :evaluation_method
7
+
8
+ def expect(expectation, evaluation_method)
9
+ @expectation = expectation
10
+ @evaluation_method = evaluation_method
11
+ end
12
+
13
+ end
14
+ extend Outpost::Expectations::ResponseBody
15
+ end
16
+
17
+ describe ".evaluate_response_body with match" do
18
+ it "should return true when it matches" do
19
+ assert SubjectBody.evaluate_response_body(scout_mock, :match => /ll/)
20
+ end
21
+
22
+ it "should return false when it doesn't" do
23
+ refute SubjectBody.evaluate_response_body(scout_mock, :match => /omg/)
24
+ end
25
+ end
26
+
27
+ describe ".evaluate_response_body with not_match" do
28
+ it "should return true when it matches" do
29
+ assert SubjectBody.evaluate_response_body(scout_mock, :not_match => /omg/)
30
+ end
31
+
32
+ it "should return false when it doesn't" do
33
+ refute SubjectBody.evaluate_response_body(scout_mock, :not_match => /Hello/)
34
+ end
35
+ end
36
+
37
+ describe ".evaluate_response_body with equals" do
38
+ it "should return true when it matches" do
39
+ assert SubjectBody.evaluate_response_body(scout_mock, :equals => "Hello!")
40
+ end
41
+
42
+ it "should return false when it doesn't" do
43
+ refute SubjectBody.evaluate_response_body(scout_mock, :equals => "Hell")
44
+ end
45
+ end
46
+
47
+ describe ".evaluate_response_body with differs" do
48
+ it "should return true when it matches" do
49
+ assert SubjectBody.evaluate_response_body(scout_mock, :differs => "Hell")
50
+ end
51
+
52
+ it "should return false when it doesn't" do
53
+ refute SubjectBody.evaluate_response_body(scout_mock, :differs => "Hello!")
54
+ end
55
+ end
56
+
57
+ describe ".evaluate_response_body with multiple rules" do
58
+ it "should return true when all rules matches" do
59
+ rules = {:differs => 'omg', :match => /ll/}
60
+ assert SubjectBody.evaluate_response_body(scout_mock, rules)
61
+ end
62
+
63
+ it "should return false when there are no matches" do
64
+ rules = {:equals => 'omg', :not_match => /ll/}
65
+ refute SubjectBody.evaluate_response_body(scout_mock, rules)
66
+ end
67
+
68
+ it "should return false when at least one rule doesn't match" do
69
+ rules = {:equals => 'Hello!', :match => /Hell/, :differs => 'Hello!'}
70
+ refute SubjectBody.evaluate_response_body(scout_mock, rules)
71
+ end
72
+ end
73
+
74
+ it "should set expectation correctly" do
75
+ assert_equal :response_body, SubjectBody.expectation
76
+ end
77
+
78
+ it "should set evaluation method correctly" do
79
+ assert_equal SubjectBody.method(:evaluate_response_body), \
80
+ SubjectBody.evaluation_method
81
+ end
82
+
83
+ private
84
+ def scout_mock
85
+ @scout_mock ||= OpenStruct.new.tap do |scout_mock|
86
+ scout_mock.response_body = 'Hello!'
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+ require 'ostruct'
3
+
4
+ describe Outpost::Expectations::ResponseCode do
5
+ class SubjectCode
6
+ class << self
7
+ attr_reader :expectation, :evaluation_method
8
+
9
+ def expect(expectation, evaluation_method)
10
+ @expectation = expectation
11
+ @evaluation_method = evaluation_method
12
+ end
13
+
14
+ end
15
+ extend Outpost::Expectations::ResponseCode
16
+ end
17
+
18
+ it "should return true when response codes match" do
19
+ assert SubjectCode.evaluate_response_code(scout_mock, 200)
20
+ end
21
+
22
+ it "should return false when response codes doesn't match" do
23
+ refute SubjectCode.evaluate_response_code(scout_mock, 404)
24
+ end
25
+
26
+ it "should convert types accordinly" do
27
+ assert SubjectCode.evaluate_response_code(scout_mock, "200")
28
+ end
29
+
30
+ it "should set expectation correctly" do
31
+ assert_equal :response_code, SubjectCode.expectation
32
+ end
33
+
34
+ it "should set evaluation method correctly" do
35
+ assert_equal SubjectCode.method(:evaluate_response_code),
36
+ SubjectCode.evaluation_method
37
+ end
38
+
39
+ private
40
+ def scout_mock
41
+ @scout_mock ||= OpenStruct.new.tap do |scout_mock|
42
+ scout_mock.response_code = 200
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,70 @@
1
+ require 'test_helper'
2
+
3
+ describe Outpost::Expectations::ResponseTime do
4
+ class SubjectTime
5
+ class << self
6
+ attr_reader :expectation, :evaluation_method
7
+
8
+ def expect(expectation, evaluation_method)
9
+ @expectation = expectation
10
+ @evaluation_method = evaluation_method
11
+ end
12
+
13
+ end
14
+ extend Outpost::Expectations::ResponseTime
15
+ end
16
+
17
+ describe ".evaluate_response_time with less_than" do
18
+ it "should return true when it matches" do
19
+ assert SubjectTime.evaluate_response_time(scout_mock, :less_than => 5000)
20
+ end
21
+
22
+ it "should return false when it doesn't" do
23
+ refute SubjectTime.evaluate_response_time(scout_mock, :less_than => 1)
24
+ end
25
+ end
26
+
27
+ describe ".evaluate_response_time with more_than" do
28
+ it "should return true when it matches" do
29
+ assert SubjectTime.evaluate_response_time(scout_mock, :more_than => 1)
30
+ end
31
+
32
+ it "should return false when it doesn't" do
33
+ refute SubjectTime.evaluate_response_time(scout_mock, :more_than => 5000)
34
+ end
35
+ end
36
+
37
+ describe ".evaluate_response_time with multiple rules" do
38
+ it "should return true when all rules matches" do
39
+ rules = {:more_than => 200, :less_than => 5000}
40
+ assert SubjectTime.evaluate_response_time(scout_mock, rules)
41
+ end
42
+
43
+ it "should return false when there are no matches" do
44
+ rules = {:more_than => 700, :less_than => 200}
45
+ refute SubjectTime.evaluate_response_time(scout_mock, rules)
46
+ end
47
+
48
+ it "should return false when at least one rule doesn't match" do
49
+ rules = {:more_than => 100, :less_than => 200}
50
+ refute SubjectTime.evaluate_response_time(scout_mock, rules)
51
+ end
52
+ end
53
+
54
+ it "should set expectation correctly" do
55
+ assert_equal :response_time, SubjectTime.expectation
56
+ end
57
+
58
+ it "should set evaluation method correctly" do
59
+ assert_equal SubjectTime.method(:evaluate_response_time),
60
+ SubjectTime.evaluation_method
61
+ end
62
+
63
+ private
64
+ def scout_mock
65
+ @scout_mock ||= OpenStruct.new.tap do |scout_mock|
66
+ scout_mock.response_time = 300
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ describe Outpost::Report do
4
+ it "should report up when all are up" do
5
+ assert_equal :up, Outpost::Report.summarize([:up, :up, :up, :up])
6
+ end
7
+
8
+ it "should report down when mixed statuses" do
9
+ assert_equal :down, Outpost::Report.summarize([:up, :down, :up, :up])
10
+ end
11
+
12
+ it "should report down when all are down" do
13
+ assert_equal :down, Outpost::Report.summarize([:down, :down, :down])
14
+ end
15
+
16
+ it "should report down when there are no statuses" do
17
+ assert_equal :down, Outpost::Report.summarize([])
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+
3
+ describe Outpost::ScoutConfig do
4
+ before(:each) do
5
+ @config = Outpost::ScoutConfig.new
6
+ end
7
+
8
+ it "should assign options accordingly" do
9
+ @config.options :host => 'localhost'
10
+
11
+ assert_equal({:host => 'localhost'}, @config.options)
12
+ end
13
+
14
+ it "should assign reports accordingly" do
15
+ @config.report :up, :response_code => 200
16
+
17
+ assert_equal({{:response_code => 200} => :up}, @config.reports)
18
+ end
19
+
20
+ it "should assign multiple reports" do
21
+ @config.report :up, :response_code => 200
22
+ @config.report :down, :response_code => 404
23
+
24
+ assert_equal({
25
+ {:response_code => 200} => :up,
26
+ {:response_code => 404} => :down,
27
+ }, @config.reports)
28
+ end
29
+ end
@@ -0,0 +1,91 @@
1
+ require 'test_helper'
2
+ require 'ostruct'
3
+
4
+ describe Outpost::Scout do
5
+ NoisyError = Class.new(StandardError)
6
+
7
+ class ScoutExample < Outpost::Scout
8
+ attr_accessor :response
9
+ expect :response, lambda {|scout, status| scout.response == status }
10
+
11
+ def setup(*args); end
12
+ def execute(*args); end
13
+ end
14
+
15
+ it "should report up when status match" do
16
+ scout = ScoutExample.new("a scout", config_mock)
17
+ scout.response = true
18
+ assert_equal :up, scout.run
19
+ end
20
+
21
+ it "should report up when status match" do
22
+ scout = ScoutExample.new("a scout", config_mock)
23
+ scout.response = false
24
+ assert_equal :down, scout.run
25
+ end
26
+
27
+ it "should not register an invalid expectation" do
28
+ assert_raises ArgumentError do
29
+ add_expectation(:invalid_expectation, nil)
30
+ end
31
+ end
32
+
33
+ it "should register a expectation using a lambda" do
34
+ add_expectation(:valid_expectation, lambda{|b| b})
35
+
36
+ refute_nil ScoutExample.expectations[:valid_expectation]
37
+ end
38
+
39
+ it "should register a expectation using pure blocks for flexibility" do
40
+ ScoutExample.expect(:valid_expectation) { |b| b }
41
+
42
+ refute_nil ScoutExample.expectations[:valid_expectation]
43
+ end
44
+
45
+ it "should not be able to have its expectations modified" do
46
+ ScoutExample.expectations[:another_expectation] = {}
47
+ assert_nil ScoutExample.expectations[:another_expectation]
48
+ end
49
+
50
+ it "should not call expectation when there are no rules for that" do
51
+ add_expectation(:noisy, proc {|s,r| raise NoisyError})
52
+ assert_nothing_raised do
53
+ scout = ScoutExample.new("a scout", config_mock)
54
+ scout.run
55
+ end
56
+ end
57
+
58
+ it "should call expectation when there are rules for that" do
59
+ add_expectation(:noisy, proc {|s,r| raise NoisyError})
60
+ config = config_mock
61
+ config.reports[{:noisy => nil}] = :down
62
+
63
+ assert_raises NoisyError do
64
+ scout = ScoutExample.new("a scout", config)
65
+ scout.run
66
+ end
67
+ end
68
+
69
+ it "should complain when an unregistered expectation is called" do
70
+ config = config_mock
71
+ config.reports[{:unregistered => nil}] = :up
72
+
73
+ assert_raises NotImplementedError do
74
+ scout = ScoutExample.new("a scout", config)
75
+ scout.run
76
+ end
77
+ end
78
+
79
+ private
80
+ def config_mock
81
+ OpenStruct.new.tap do |config|
82
+ config.reports = {}
83
+ config.reports[{:response => true}] = :up
84
+ config.reports[{:response => false}] = :down
85
+ end
86
+ end
87
+
88
+ def add_expectation(expectation, callable)
89
+ ScoutExample.expect expectation, callable
90
+ end
91
+ end
@@ -0,0 +1,24 @@
1
+ require 'rack/handler/thin'
2
+ require 'net/http'
3
+
4
+ class Server
5
+ # Got it from Capybara, but simplified it a bit.
6
+ # lib/capybara/server.rb
7
+ def responsive?
8
+ res = Net::HTTP.start('localhost', 9595) { |http| http.get('/') }
9
+
10
+ res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
11
+ rescue Errno::ECONNREFUSED, Errno::EBADF
12
+ return false
13
+ end
14
+
15
+ def boot(app)
16
+ if not responsive?
17
+ Thread.new do
18
+ Thin::Logging.silent = true
19
+ Rack::Handler::Thin.run(app, :Port => 9595)
20
+ end
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'sinatra/base'
2
+
3
+ class TestApp < Sinatra::Base
4
+ get '/' do
5
+ [200, 'Up and running!']
6
+ end
7
+
8
+ get '/fail' do
9
+ [500, 'Omg fail']
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ require 'bundler'
2
+ Bundler.setup(:default, :test)
3
+
4
+ require 'ruby-debug'
5
+ require 'minitest/spec'
6
+ require 'minitest/autorun'
7
+
8
+ # Integration test helpers
9
+ require 'support/test_app'
10
+ require 'support/server'
11
+
12
+ require 'outpost'
13
+ require 'outpost/expectations'
14
+
15
+ # Inspired by assert_raises from minitest
16
+ def assert_nothing_raised(&block)
17
+ block.call
18
+ rescue Exception => e
19
+ flunk "No exception expected, but #{mu_pp(e.class)} was raised."
20
+ end
21
+
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: outpost
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Vinicius Baggio Fuentes
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-01 00:00:00 -02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: net-ping
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 3
31
+ - 7
32
+ version: 1.3.7
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Simple service monitoring with a clean DSL for configuration.
36
+ email: vinibaggio@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - Gemfile
45
+ - Gemfile.lock
46
+ - README.markdown
47
+ - Rakefile
48
+ - lib/outpost.rb
49
+ - lib/outpost/dsl.rb
50
+ - lib/outpost/expectations.rb
51
+ - lib/outpost/expectations/response_body.rb
52
+ - lib/outpost/expectations/response_code.rb
53
+ - lib/outpost/expectations/response_time.rb
54
+ - lib/outpost/report.rb
55
+ - lib/outpost/scout.rb
56
+ - lib/outpost/scout_config.rb
57
+ - lib/outpost/scouts.rb
58
+ - lib/outpost/scouts/http.rb
59
+ - lib/outpost/scouts/ping.rb
60
+ - lib/outpost/version.rb
61
+ - outpost.gemspec
62
+ - test/integration/basic_dsl_test.rb
63
+ - test/integration/more_complex_test.rb
64
+ - test/outpost/dsl_test.rb
65
+ - test/outpost/expectations/response_body_test.rb
66
+ - test/outpost/expectations/response_code_test.rb
67
+ - test/outpost/expectations/response_time_test.rb
68
+ - test/outpost/report_test.rb
69
+ - test/outpost/scout_config_test.rb
70
+ - test/outpost/scout_test.rb
71
+ - test/support/server.rb
72
+ - test/support/test_app.rb
73
+ - test/test_helper.rb
74
+ has_rdoc: true
75
+ homepage: http://www.github.com/vinibaggio/outpost
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project: outpost
102
+ rubygems_version: 1.3.7
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Simple service monitoring with a clean DSL for configuration.
106
+ test_files:
107
+ - test/integration/basic_dsl_test.rb
108
+ - test/integration/more_complex_test.rb
109
+ - test/outpost/dsl_test.rb
110
+ - test/outpost/expectations/response_body_test.rb
111
+ - test/outpost/expectations/response_code_test.rb
112
+ - test/outpost/expectations/response_time_test.rb
113
+ - test/outpost/report_test.rb
114
+ - test/outpost/scout_config_test.rb
115
+ - test/outpost/scout_test.rb
116
+ - test/support/server.rb
117
+ - test/support/test_app.rb
118
+ - test/test_helper.rb