onesnooper-server 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +3 -0
- data/LICENSE +202 -0
- data/README.md +115 -0
- data/Rakefile +26 -0
- data/bin/onesnooper-server +45 -0
- data/config/onesnooper-server.yml +41 -0
- data/examples/monitoring.data +66 -0
- data/examples/send-dummy-data +39 -0
- data/lib/onesnooper-server.rb +32 -0
- data/lib/onesnooper_server/datagram.rb +19 -0
- data/lib/onesnooper_server/datagrams.rb +7 -0
- data/lib/onesnooper_server/datagrams/failure_datagram.rb +9 -0
- data/lib/onesnooper_server/datagrams/invalid_datagram.rb +9 -0
- data/lib/onesnooper_server/datagrams/success_datagram.rb +60 -0
- data/lib/onesnooper_server/log.rb +74 -0
- data/lib/onesnooper_server/payload_parser.rb +143 -0
- data/lib/onesnooper_server/request_handler.rb +85 -0
- data/lib/onesnooper_server/settings.rb +12 -0
- data/lib/onesnooper_server/sql_store.rb +65 -0
- data/lib/onesnooper_server/store.rb +23 -0
- data/lib/onesnooper_server/stores.rb +7 -0
- data/lib/onesnooper_server/stores/invalid_store.rb +10 -0
- data/lib/onesnooper_server/stores/mongodb_store.rb +23 -0
- data/lib/onesnooper_server/stores/mysql_store.rb +17 -0
- data/lib/onesnooper_server/stores/sqlite_store.rb +10 -0
- data/lib/onesnooper_server/udp_handler.rb +50 -0
- data/lib/onesnooper_server/version.rb +4 -0
- data/migrations/001_create_db.rb +60 -0
- data/onesnooper-server.gemspec +41 -0
- data/spec/helpers/.keep +0 -0
- data/spec/onesnooper_server_spec.rb +7 -0
- data/spec/spec_helper.rb +13 -0
- metadata +265 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
# Request handler for validating and dispatching
|
2
|
+
# datagram processing on defined types of datagrams.
|
3
|
+
# Invalid or unknown datagrams will be processed by
|
4
|
+
# the `OnesnooperServer::Datagrams::InvalidDatagram`
|
5
|
+
# sub-handler.
|
6
|
+
class OnesnooperServer::RequestHandler
|
7
|
+
|
8
|
+
# Validation constant for incoming datagrams
|
9
|
+
MONITORING_DATA_REGEXP = /^MONITOR\s(?<result>[[:alpha:]]+)\s(?<host_id>\d+)\s(?<payload>\S+)$/
|
10
|
+
|
11
|
+
# Registered allowed datagram processing classes & the default fallback
|
12
|
+
DATAGRAMS = {
|
13
|
+
"SUCCESS" => ::OnesnooperServer::Datagrams::SuccessDatagram,
|
14
|
+
"FAILURE" => ::OnesnooperServer::Datagrams::FailureDatagram,
|
15
|
+
}
|
16
|
+
DATAGRAMS.default = ::OnesnooperServer::Datagrams::InvalidDatagram
|
17
|
+
|
18
|
+
# Registered allowed stores for monitoring data
|
19
|
+
STORES = {
|
20
|
+
"mongodb" => ::OnesnooperServer::Stores::MongodbStore,
|
21
|
+
"mysql" => ::OnesnooperServer::Stores::MysqlStore,
|
22
|
+
"sqlite" => ::OnesnooperServer::Stores::SqliteStore,
|
23
|
+
}
|
24
|
+
STORES.default = ::OnesnooperServer::Stores::InvalidStore
|
25
|
+
|
26
|
+
# Static parsing method for identifying types of incoming datagrams
|
27
|
+
# and choosing the right processing class for each datagram. Always
|
28
|
+
# returns an instance responding to `run(callback)`.
|
29
|
+
#
|
30
|
+
# @param monitoring_datagram [Object] datagram payload for processing
|
31
|
+
# @param source_ip [String] IP address of the client
|
32
|
+
# @param source_port [String] port number of the client
|
33
|
+
def self.parse(monitoring_datagram, source_ip, source_port)
|
34
|
+
unless valid_data?(monitoring_datagram)
|
35
|
+
::OnesnooperServer::Log.fatal "[#{self.name}] Dropping invalid monitoring data #{monitoring_datagram.inspect}"
|
36
|
+
return DATAGRAMS.default.new
|
37
|
+
end
|
38
|
+
|
39
|
+
unless valid_peer?(source_ip)
|
40
|
+
::OnesnooperServer::Log.warn "[#{self.name}] Dropping monitoring data from #{source_ip}, not allowed!"
|
41
|
+
return DATAGRAMS.default.new
|
42
|
+
end
|
43
|
+
|
44
|
+
match_data = monitoring_datagram.match(MONITORING_DATA_REGEXP)
|
45
|
+
return DATAGRAMS.default.new unless match_data
|
46
|
+
|
47
|
+
DATAGRAMS[match_data[:result]].new({
|
48
|
+
host_id: match_data[:host_id],
|
49
|
+
payload: match_data[:payload],
|
50
|
+
stores: store_instances,
|
51
|
+
})
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Identifies incoming datagrams as valid for further processing.
|
57
|
+
# Datagrams must be non-nil and String-like.
|
58
|
+
#
|
59
|
+
# @param monitoring_datagram [Object] incoming datagram for validation
|
60
|
+
# @return [Boolean] result
|
61
|
+
def self.valid_data?(monitoring_datagram)
|
62
|
+
monitoring_datagram && monitoring_datagram.kind_of?(String)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Matches provided IP address against the list of known
|
66
|
+
# allowed peers.
|
67
|
+
#
|
68
|
+
# @param source_ip [String] IP address to match
|
69
|
+
# @return [Boolean] result
|
70
|
+
def self.valid_peer?(source_ip)
|
71
|
+
::OnesnooperServer::Settings.allowed_sources.include? source_ip
|
72
|
+
end
|
73
|
+
|
74
|
+
# Retrieves a list of instances for allowed store backends.
|
75
|
+
#
|
76
|
+
# @return [Array] list of store instances
|
77
|
+
def self.store_instances
|
78
|
+
::OnesnooperServer::Settings.store.collect do |store_name|
|
79
|
+
STORES[store_name].new(
|
80
|
+
::OnesnooperServer::Settings.stores.respond_to?(store_name) ? ::OnesnooperServer::Settings.stores.send(store_name) : {}
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Wraps persistent setting taken from configuration
|
2
|
+
# files. This singleton is available throughout the
|
3
|
+
# whole application environment.
|
4
|
+
class OnesnooperServer::Settings < ::Settingslogic
|
5
|
+
gem_root = File.expand_path '../../..', __FILE__
|
6
|
+
|
7
|
+
source "#{ENV['HOME']}/.onesnooper-server" if File.readable?("#{ENV['HOME']}/.onesnooper-server")
|
8
|
+
source "/etc/onesnooper-server/onesnooper-server.yml" if File.readable?("/etc/onesnooper-server/onesnooper-server.yml")
|
9
|
+
source "#{gem_root}/config/onesnooper-server.yml"
|
10
|
+
|
11
|
+
namespace ENV['RAILS_ENV'] ? ENV['RAILS_ENV'] : 'production'
|
12
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
# Class wrapping common features of all SQL-based
|
4
|
+
# stores. Using 'sequel' to abstract differences.
|
5
|
+
class OnesnooperServer::SqlStore < ::OnesnooperServer::Store
|
6
|
+
|
7
|
+
# constant table name
|
8
|
+
SQL_TABLE_NAME = :one_monitoring
|
9
|
+
|
10
|
+
def save!(timestamp, data)
|
11
|
+
::OnesnooperServer::Log.debug "[#{self.class.name}] Saving #{timestamp.to_s} => #{data.inspect}"
|
12
|
+
fail "DB connection has to be initialized from subclasses, " \
|
13
|
+
"::OnesnooperServer::SqlStore cannot be used directly!" unless @db_conn
|
14
|
+
|
15
|
+
if insert_data = data_in_vm_groups(timestamp, data)
|
16
|
+
@db_conn[SQL_TABLE_NAME].multi_insert(insert_data)
|
17
|
+
else
|
18
|
+
::OnesnooperServer::Log.warn "[#{self.class.name}] Skipping SQL INSERT for an empty dataset"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Converts parsed datagram payload into table rows
|
25
|
+
# for direct insert into the DB.
|
26
|
+
#
|
27
|
+
# @param timestamp [DateTime] payload time stamp
|
28
|
+
# @param data [Hash] hash-like parsed payload structure
|
29
|
+
# @return [Array, NilClass] array with row hashes
|
30
|
+
def data_in_vm_groups(timestamp, data)
|
31
|
+
data_ary = []
|
32
|
+
|
33
|
+
data['VM'].each do |vm_on_host|
|
34
|
+
next if vm_on_host.blank?
|
35
|
+
data_ary << {
|
36
|
+
:timestamp => timestamp,
|
37
|
+
:vm_id => vm_on_host['ID'],
|
38
|
+
:vm_deploy_id => vm_on_host['DEPLOY_ID'],
|
39
|
+
:vm_netrx => vm_on_host['POLL']['NETRX'],
|
40
|
+
:vm_nettx => vm_on_host['POLL']['NETTX'],
|
41
|
+
:vm_used_cpu => vm_on_host['POLL']['USEDCPU'],
|
42
|
+
:vm_used_memory => vm_on_host['POLL']['USEDMEMORY'],
|
43
|
+
:vm_state => vm_on_host['POLL']['STATE'],
|
44
|
+
:host_name => data['HOSTNAME'],
|
45
|
+
:host_arch => data['ARCH'],
|
46
|
+
:host_model => data['MODELNAME'],
|
47
|
+
:host_hypervisor => data['HYPERVISOR'],
|
48
|
+
:host_ds_total => data['DS_LOCATION_TOTAL_MB'],
|
49
|
+
:host_ds_used => data['DS_LOCATION_USED_MB'],
|
50
|
+
:host_ds_free => data['DS_LOCATION_FREE_MB'],
|
51
|
+
:host_total_cpu => data['TOTALCPU'],
|
52
|
+
:host_cpu_speed => data['CPUSPEED'],
|
53
|
+
:host_used_cpu => data['USEDCPU'],
|
54
|
+
:host_free_cpu => data['FREECPU'],
|
55
|
+
:host_total_memory => data['TOTALMEMORY'],
|
56
|
+
:host_free_memory => data['FREEMEMORY'],
|
57
|
+
:host_used_memory => data['USEDMEMORY'],
|
58
|
+
:one_version => data['VERSION'],
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
data_ary.empty? ? nil : data_ary
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Base class for all backend data stores. Implements key
|
2
|
+
# method stubs required for all specific backend data store
|
3
|
+
# implementations.
|
4
|
+
class OnesnooperServer::Store
|
5
|
+
|
6
|
+
# Initializes data store instance with given parameters.
|
7
|
+
#
|
8
|
+
# @param params [Hash] hash-like structure with parameters
|
9
|
+
def initialize(params = {})
|
10
|
+
@params = params
|
11
|
+
end
|
12
|
+
|
13
|
+
# Saves given data set into the underlying data store.
|
14
|
+
# Behavior is determined by the underlying data store
|
15
|
+
# implementation.
|
16
|
+
#
|
17
|
+
# @param timestamp [DateTime] current time
|
18
|
+
# @param data [Hash] data to be saved in the data store
|
19
|
+
def save!(timestamp, data)
|
20
|
+
fail "This method needs to be implemented in subclasses"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Module housing available backend implementations for
|
2
|
+
# data stores. All stores must implement basic methods
|
3
|
+
# outlined in the `OnesnooperServer::Store` class.
|
4
|
+
module OnesnooperServer::Stores; end
|
5
|
+
|
6
|
+
# Load all available store types
|
7
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'stores', "*.rb")) { |store_file| require store_file.chomp('.rb') }
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Placeholder store for invalid configuration handling and
|
2
|
+
# testing purposes.
|
3
|
+
class OnesnooperServer::Stores::InvalidStore < ::OnesnooperServer::Store
|
4
|
+
|
5
|
+
def save!(timestamp, data)
|
6
|
+
::OnesnooperServer::Log.fatal "[#{self.class.name}] This is a dummy store, do not use it!"
|
7
|
+
fail "InvalidStore selected as fallback, check your configuration!"
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'mongo'
|
2
|
+
|
3
|
+
# MongoDB-based store for production deployments and dynamic
|
4
|
+
# document structure.
|
5
|
+
class OnesnooperServer::Stores::MongodbStore < ::OnesnooperServer::Store
|
6
|
+
|
7
|
+
# constant collection name
|
8
|
+
MONGO_COLL_NAME = 'one_monitoring'
|
9
|
+
|
10
|
+
def initialize(params = {})
|
11
|
+
super
|
12
|
+
@db_conn = ::Mongo::MongoClient.new(params[:host], params[:port])
|
13
|
+
@db_active_db = @db_conn.db(params[:database])
|
14
|
+
@db_coll = @db_active_db.create_collection(MONGO_COLL_NAME)
|
15
|
+
end
|
16
|
+
|
17
|
+
def save!(timestamp, data)
|
18
|
+
::OnesnooperServer::Log.debug "[#{self.class.name}] Saving #{timestamp.to_s} => #{data.inspect}"
|
19
|
+
data['TIMESTAMP'] = timestamp.to_time.utc
|
20
|
+
@db_coll.insert data
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# MySQL-based store for production deployments with static
|
2
|
+
# table structure.
|
3
|
+
class OnesnooperServer::Stores::MysqlStore < ::OnesnooperServer::SqlStore
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
super
|
7
|
+
@db_conn = ::Sequel.connect(
|
8
|
+
:adapter => 'mysql2',
|
9
|
+
:database => params[:database],
|
10
|
+
:user => params[:username],
|
11
|
+
:password => params[:password],
|
12
|
+
:host => params[:host],
|
13
|
+
:port => params[:port],
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Sqlite3-based store for testing and small deployments.
|
2
|
+
class OnesnooperServer::Stores::SqliteStore < ::OnesnooperServer::SqlStore
|
3
|
+
|
4
|
+
def initialize(params = {})
|
5
|
+
super
|
6
|
+
@db_path = "sqlite://#{params[:database_file]}"
|
7
|
+
@db_conn = ::Sequel.connect(@db_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Handler for incoming UDP datagrams. Implements required methods
|
2
|
+
# for direct use with EventMachine listeners. This handler will not
|
3
|
+
# respond to incoming datagrams as its primary purpose is to record
|
4
|
+
# mirrored monitoring traffic.
|
5
|
+
class OnesnooperServer::UDPHandler < ::EventMachine::Connection
|
6
|
+
|
7
|
+
DATAGRAM_PREFIX = 'MONITOR'
|
8
|
+
|
9
|
+
# Receives data and triggers processing of the given
|
10
|
+
# datagram. Main internal processing triggered from this
|
11
|
+
# method should always happen asynchronously (i.e., using
|
12
|
+
# EventMachine.defer or Deferrable classes).
|
13
|
+
#
|
14
|
+
# @param monitoring_datagram [String] incoming data payload
|
15
|
+
def receive_data(monitoring_datagram)
|
16
|
+
monitoring_datagram.chomp!
|
17
|
+
source_port, source_ip = Socket.unpack_sockaddr_in(get_peername)
|
18
|
+
unless monitoring_datagram.start_with?(DATAGRAM_PREFIX)
|
19
|
+
::OnesnooperServer::Log.warn "[#{self.class.name}] Discarding datagram from " \
|
20
|
+
"#{source_ip}:#{source_port} (not #{DATAGRAM_PREFIX})"
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
::OnesnooperServer::Log.debug "[#{self.class.name}] Received #{monitoring_datagram.inspect} " \
|
25
|
+
"from #{source_ip}:#{source_port}"
|
26
|
+
::OnesnooperServer::RequestHandler.parse(
|
27
|
+
monitoring_datagram,
|
28
|
+
source_ip,
|
29
|
+
source_port
|
30
|
+
).run(callback)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Callable processing callback. As this implementation does
|
36
|
+
# not send responses to incoming datagrams, this methods is
|
37
|
+
# for logging and debugging purposes only.
|
38
|
+
def callback
|
39
|
+
def_deferr = ::EventMachine::DefaultDeferrable.new
|
40
|
+
proc_callback = Proc.new { |response| ::OnesnooperServer::Log.debug(
|
41
|
+
"[#{self.class.name}] Handled as: #{response}"
|
42
|
+
) }
|
43
|
+
|
44
|
+
def_deferr.callback &proc_callback
|
45
|
+
def_deferr.errback &proc_callback
|
46
|
+
|
47
|
+
def_deferr
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# -------------------------------------------------------------------------- #
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
5
|
+
# not use this file except in compliance with the License. You may obtain #
|
6
|
+
# a copy of the License at #
|
7
|
+
# #
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0 #
|
9
|
+
# #
|
10
|
+
# Unless required by applicable law or agreed to in writing, software #
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, #
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
13
|
+
# See the License for the specific language governing permissions and #
|
14
|
+
# limitations under the License. #
|
15
|
+
#--------------------------------------------------------------------------- #
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'sequel'
|
19
|
+
|
20
|
+
Sequel.migration do
|
21
|
+
up do
|
22
|
+
create_table(:one_monitoring) do
|
23
|
+
#
|
24
|
+
primary_key :id, :type => Bignum
|
25
|
+
DateTime :timestamp, :null => false, :index => true
|
26
|
+
|
27
|
+
#
|
28
|
+
String :vm_id, :null => false, :index => true
|
29
|
+
String :vm_deploy_id, :null => false, :index => true
|
30
|
+
BigDecimal :vm_netrx, :default => 0
|
31
|
+
BigDecimal :vm_nettx, :default => 0
|
32
|
+
Float :vm_used_cpu, :default => 0.0
|
33
|
+
BigDecimal :vm_used_memory, :default => 0
|
34
|
+
String :vm_state, :null => false, :index => true
|
35
|
+
|
36
|
+
#
|
37
|
+
String :host_name, :null => false, :index => true
|
38
|
+
String :host_arch, :default => 'unknown'
|
39
|
+
String :host_model, :default => 'unknown'
|
40
|
+
String :host_hypervisor, :null => false
|
41
|
+
BigDecimal :host_ds_total, :default => 0
|
42
|
+
BigDecimal :host_ds_used, :default => 0
|
43
|
+
BigDecimal :host_ds_free, :default => 0
|
44
|
+
Integer :host_total_cpu, :null => false
|
45
|
+
Integer :host_cpu_speed, :default => 0
|
46
|
+
Integer :host_used_cpu, :default => 0
|
47
|
+
Integer :host_free_cpu, :default => 0
|
48
|
+
BigDecimal :host_total_memory, :null => false
|
49
|
+
BigDecimal :host_free_memory, :default => 0
|
50
|
+
BigDecimal :host_used_memory, :default => 0
|
51
|
+
|
52
|
+
#
|
53
|
+
String :one_version, :null => false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
down do
|
58
|
+
drop_table(:one_monitoring)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'onesnooper_server/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'onesnooper-server'
|
8
|
+
spec.version = OnesnooperServer::VERSION
|
9
|
+
spec.authors = ['Boris Parak']
|
10
|
+
spec.email = ['parak@cesnet.cz']
|
11
|
+
spec.summary = 'Simple server snooping on and recording OpenNebula\'s VM & Host monitoring traffic'
|
12
|
+
spec.description = 'Simple server snooping on and recording OpenNebula\'s VM & Host monitoring traffic'
|
13
|
+
spec.homepage = 'https://github.com/arax/onesnooper-server'
|
14
|
+
spec.license = 'Apache License, Version 2.0'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
spec.required_ruby_version = '>= 1.9.3'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
25
|
+
spec.add_development_dependency 'simplecov', '~> 0.9.0'
|
26
|
+
spec.add_development_dependency 'rubygems-tasks', '~> 0.2.4'
|
27
|
+
|
28
|
+
# internals
|
29
|
+
spec.add_runtime_dependency 'eventmachine', '~> 1.0.4'
|
30
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.2.0'
|
31
|
+
spec.add_runtime_dependency 'settingslogic', '~> 2.0.9'
|
32
|
+
|
33
|
+
# SQL DB connectors
|
34
|
+
spec.add_runtime_dependency 'sequel', '~> 4.18.0'
|
35
|
+
spec.add_runtime_dependency 'sqlite3', '~> 1.3.10'
|
36
|
+
spec.add_runtime_dependency 'mysql2', '~> 0.3.17'
|
37
|
+
|
38
|
+
# NoSQL DB connectors
|
39
|
+
spec.add_runtime_dependency 'mongo', '~> 1.11.1'
|
40
|
+
spec.add_runtime_dependency 'bson_ext', '~> 1.11.1'
|
41
|
+
end
|
data/spec/helpers/.keep
ADDED
File without changes
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
# enable coverage reports
|
4
|
+
if ENV['COVERAGE']
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
SimpleCov.add_filter "/spec/"
|
8
|
+
SimpleCov.start
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'onesnooper-server'
|
12
|
+
|
13
|
+
Dir["#{File.dirname(__FILE__)}/helpers/*.rb"].each {|file| require file }
|