outpost 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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