onesnooper-server 0.0.1
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.
- 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 }
|