overwatch-collection 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.watchr +18 -0
- data/Gemfile +33 -0
- data/Gemfile.lock +104 -0
- data/README.md +26 -0
- data/Rakefile +57 -0
- data/bin/overwatch-collection +38 -0
- data/config.ru +9 -0
- data/config/overwatch.yml +6 -0
- data/lib/..rb +5 -0
- data/lib/overwatch/collection.rb +42 -0
- data/lib/overwatch/collection/application.rb +46 -0
- data/lib/overwatch/collection/attributes.rb +136 -0
- data/lib/overwatch/collection/models/resource.rb +50 -0
- data/lib/overwatch/collection/models/snapshot.rb +88 -0
- data/lib/overwatch/collection/routes/resource.rb +107 -0
- data/lib/overwatch/collection/routes/snapshot.rb +66 -0
- data/lib/overwatch/collection/version.rb +5 -0
- data/log/.gitkeep +0 -0
- data/overwatch-collection.gemspec +43 -0
- data/spec/overwatch/collection/application_spec.rb +0 -0
- data/spec/overwatch/collection/attributes_spec.rb +64 -0
- data/spec/overwatch/collection/helpers_spec.rb +0 -0
- data/spec/overwatch/collection/models/resource_spec.rb +60 -0
- data/spec/overwatch/collection/models/snapshot_spec.rb +32 -0
- data/spec/overwatch/collection/routes/resource_spec.rb +146 -0
- data/spec/overwatch/collection/routes/snapshot_spec.rb +161 -0
- data/spec/overwatch/collection_spec.rb +0 -0
- data/spec/overwatch_spec.rb +2 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/json.rb +3 -0
- data/spec/support/respond_with_matchers.rb +33 -0
- data/spec/support/snapshot_data.rb +7 -0
- data/spec/support/time_travel.rb +3 -0
- metadata +304 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module Overwatch
|
2
|
+
class Resource
|
3
|
+
include DataMapper::Resource
|
4
|
+
include Overwatch::Collection::Attributes
|
5
|
+
|
6
|
+
property :id, Serial, :index => true
|
7
|
+
property :name, String, :index => true
|
8
|
+
property :api_key, String, :index => true
|
9
|
+
property :created_at, DateTime
|
10
|
+
property :updated_at, DateTime
|
11
|
+
|
12
|
+
has n, :snapshots
|
13
|
+
|
14
|
+
before :create, :generate_api_key
|
15
|
+
|
16
|
+
validates_presence_of :name
|
17
|
+
validates_uniqueness_of :name
|
18
|
+
validates_uniqueness_of :api_key
|
19
|
+
|
20
|
+
# after :create, :schedule_no_data_check
|
21
|
+
|
22
|
+
def previous_update
|
23
|
+
snapshots[-2]
|
24
|
+
end
|
25
|
+
|
26
|
+
def last_update
|
27
|
+
snapshots[-1]
|
28
|
+
end
|
29
|
+
#
|
30
|
+
# def run_checks
|
31
|
+
# Resque.enqueue(CheckRun, self.id.to_s)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def schedule_no_data_check
|
35
|
+
# Resque.enqueue_in(5.minutes, NoDataCheck, self)
|
36
|
+
# end
|
37
|
+
|
38
|
+
def regenerate_api_key
|
39
|
+
generate_api_key
|
40
|
+
self.save
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def generate_api_key
|
46
|
+
api_key = Digest::SHA1.hexdigest(Time.now.to_s + rand(12341234).to_s)[1..30]
|
47
|
+
self.api_key = api_key
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Overwatch
|
2
|
+
class Snapshot
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial, :index => true
|
6
|
+
property :data, Json
|
7
|
+
property :created_at, DateTime
|
8
|
+
property :updated_at, DateTime
|
9
|
+
property :created_on, Date
|
10
|
+
property :updated_on, Date
|
11
|
+
property :created_at_hour, Integer
|
12
|
+
property :created_at_min, Integer
|
13
|
+
property :created_at_int, Integer
|
14
|
+
|
15
|
+
belongs_to :resource
|
16
|
+
|
17
|
+
# attr_accessor :data
|
18
|
+
attr_accessor :raw_data
|
19
|
+
|
20
|
+
after :create, :parse_data
|
21
|
+
# after :create, :run_checks
|
22
|
+
after :create, :update_attribute_keys
|
23
|
+
after :create, :generate_timestamps
|
24
|
+
# after :create, :schedule_reaper
|
25
|
+
|
26
|
+
def data
|
27
|
+
begin
|
28
|
+
Hashie::Mash.new(
|
29
|
+
Yajl.load($redis.hget("overwatch::snapshot:#{self.id}", "data"))
|
30
|
+
)
|
31
|
+
rescue
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_checks
|
36
|
+
self.resource.run_checks
|
37
|
+
end
|
38
|
+
|
39
|
+
def schedule_reaper
|
40
|
+
if self.created_at.min % 5 != 0
|
41
|
+
Resque.enqueue_in(60.minutes, SnapshotReaper, self)
|
42
|
+
# Resque.enqueue_in(60.minutes, AttributeReaper, "resource:#{self.resource_id}:#{key}")
|
43
|
+
elsif self.created_at.hour != 0 && self.created_at.min != 0
|
44
|
+
Resque.enqueue_in(1.day, SnapshotReaper, self)
|
45
|
+
else
|
46
|
+
Resque.enqueue_in(30.days, SnapshotReaper, self)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_dotted_hash(source=self.data,target = {}, namespace = nil)
|
51
|
+
prefix = "#{namespace}." if namespace
|
52
|
+
case source
|
53
|
+
when Hash
|
54
|
+
source.each do |key, value|
|
55
|
+
to_dotted_hash(value, target, "#{prefix}#{key}")
|
56
|
+
end
|
57
|
+
else
|
58
|
+
target[namespace] = source
|
59
|
+
end
|
60
|
+
target
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def parse_data
|
66
|
+
self.to_dotted_hash.each_pair do |key, value|
|
67
|
+
timestamp = self.created_at.to_i * 1000
|
68
|
+
$redis.zadd "overwatch::resource:#{resource.id}:#{key}", timestamp, "#{timestamp}:#{value}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_attribute_keys
|
73
|
+
self.to_dotted_hash.keys.each do |key|
|
74
|
+
$redis.sadd "overwatch::resource:#{self.resource.id}:attribute_keys", key
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_timestamps
|
79
|
+
self.created_at_int = self.created_at.to_i
|
80
|
+
self.save
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class AttributeError < Exception; end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Overwatch
|
2
|
+
module Collection
|
3
|
+
class Application < Sinatra::Base
|
4
|
+
|
5
|
+
get '/resources/?' do
|
6
|
+
resources = Resource.all
|
7
|
+
if resources.size < 1
|
8
|
+
[].to_json
|
9
|
+
else
|
10
|
+
resources.to_json
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/resources/:id/?' do |id|
|
15
|
+
resource = Resource.get(id)
|
16
|
+
if resource
|
17
|
+
status 200
|
18
|
+
resource.to_json
|
19
|
+
else
|
20
|
+
halt 404
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
post '/resources/?' do
|
26
|
+
data = Yajl.load(request.body.read)
|
27
|
+
resource = Resource.new(:name => data['name'])
|
28
|
+
if resource.save
|
29
|
+
status 201
|
30
|
+
resource.to_json
|
31
|
+
else
|
32
|
+
status 422
|
33
|
+
resource.errors.to_json
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
put '/resources/:id/?' do |id|
|
38
|
+
data = Yajl.load(request.body.read)
|
39
|
+
resource = Resource.get(id)
|
40
|
+
if resource
|
41
|
+
if resource.update(data)
|
42
|
+
status 200
|
43
|
+
resource.to_json
|
44
|
+
else
|
45
|
+
status 409
|
46
|
+
resource.errors.to_json
|
47
|
+
end
|
48
|
+
else
|
49
|
+
halt 404
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
delete '/resources/:id/?' do |id|
|
55
|
+
resource = Resource.get(id)
|
56
|
+
if resource
|
57
|
+
if resource.destroy
|
58
|
+
status 200
|
59
|
+
else
|
60
|
+
status 409
|
61
|
+
resource.errors.to_json
|
62
|
+
end
|
63
|
+
else
|
64
|
+
halt 404
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
get '/resources/:id/attributes/?' do |id|
|
70
|
+
resource = Resource.get(id)
|
71
|
+
attributes = resource.attribute_keys
|
72
|
+
if attributes.length < 1
|
73
|
+
[].to_json
|
74
|
+
else
|
75
|
+
attributes.sort.to_json
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
get '/resources/:id/attributes/:attribute/?' do |id, attribute|
|
80
|
+
resource = Resource.get(id)
|
81
|
+
|
82
|
+
options = {
|
83
|
+
:start_at => (params[:start_at].to_i || nil),
|
84
|
+
:end_at => (params[:end_at].to_i || nil),
|
85
|
+
:interval => (params[:interval].to_s || 'hour')
|
86
|
+
}
|
87
|
+
|
88
|
+
values = resource.values_with_dates_for(attribute, options)
|
89
|
+
values.to_json
|
90
|
+
end
|
91
|
+
|
92
|
+
get '/resources/:id/attributes/:attribute/:function/?' do |id, attribute, function|
|
93
|
+
resource = Resource.get(id)
|
94
|
+
|
95
|
+
options = {
|
96
|
+
:start_at => (params[:start_at].to_i || nil),
|
97
|
+
:end_at => (params[:end_at].to_i || nil),
|
98
|
+
:interval => (params[:interval].to_s || 'hour')
|
99
|
+
}
|
100
|
+
|
101
|
+
attribute = resource.function(params[:function].to_sym, attribute, options)
|
102
|
+
attribute.to_json
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Overwatch
|
2
|
+
module Collection
|
3
|
+
class Application < Sinatra::Base
|
4
|
+
|
5
|
+
get '/resources/:resource_id/snapshots/?' do |resource_id|
|
6
|
+
resource = Resource.get(resource_id)
|
7
|
+
snapshots = resource.snapshots
|
8
|
+
|
9
|
+
if snapshots.size < 1
|
10
|
+
[].to_json
|
11
|
+
else
|
12
|
+
snapshots.to_json
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/resources/:resource_id/snapshots/:id/?' do |resource_id, id|
|
17
|
+
resource = Resource.get(resource_id)
|
18
|
+
snapshot = resource.snapshots.first(:id => id)
|
19
|
+
|
20
|
+
if resource
|
21
|
+
if snapshot
|
22
|
+
status 200
|
23
|
+
snapshot.to_json
|
24
|
+
else
|
25
|
+
halt 404
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
post '/snapshots/?' do
|
31
|
+
resource = Resource.first(:api_key => params['key'])
|
32
|
+
data = Yajl.load(request.body.read)
|
33
|
+
snapshot = resource.snapshots.create(:data => data)
|
34
|
+
|
35
|
+
if resource
|
36
|
+
if snapshot
|
37
|
+
status 201
|
38
|
+
snapshot.to_json
|
39
|
+
else
|
40
|
+
status 409
|
41
|
+
snapshot.errors.to_json
|
42
|
+
end
|
43
|
+
else
|
44
|
+
halt 404
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
get '/resources/:resource_id/snapshots/:id/data/?' do |resource_id, id|
|
49
|
+
resource = Resource.get(resource_id)
|
50
|
+
snapshot = resource.snapshots.first(:id => id)
|
51
|
+
|
52
|
+
if resource
|
53
|
+
if snapshot
|
54
|
+
status 200
|
55
|
+
snapshot.data.to_json
|
56
|
+
else
|
57
|
+
halt 404
|
58
|
+
end
|
59
|
+
else
|
60
|
+
halt 404
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/log/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'overwatch/collection/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'overwatch-collection'
|
8
|
+
s.version = Overwatch::Collection::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = [ "Dan Ryan" ]
|
11
|
+
s.email = [ "dan@appliedawesome.com" ]
|
12
|
+
s.homepage = "https://github.com/danryan/overwatch-collection"
|
13
|
+
s.summary = "Overwatch statistical time series collection app"
|
14
|
+
s.description = "overwatch-collection is a Redis-backed statistical time series collection application designed to be fast, extensible, and easy to use."
|
15
|
+
|
16
|
+
s.rubyforge_project = "overwatch-collection"
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'dm-core', '>= 1.1.0'
|
23
|
+
s.add_dependency 'dm-active_model', '>= 1.1.0'
|
24
|
+
s.add_dependency 'dm-redis-adapter', '>= 0.4.0'
|
25
|
+
s.add_dependency 'dm-serializer', '>= 1.1.0'
|
26
|
+
s.add_dependency 'dm-timestamps', '>= 1.1.0'
|
27
|
+
s.add_dependency 'dm-validations', '>= 1.1.0'
|
28
|
+
s.add_dependency 'dm-types', '>= 1.1.0'
|
29
|
+
s.add_dependency 'yajl-ruby', '>= 0.8.2'
|
30
|
+
s.add_dependency 'hashie', '>= 1.0.0'
|
31
|
+
s.add_dependency 'rest-client', '>= 1.6.3'
|
32
|
+
s.add_dependency 'sinatra', '>= 1.2.6'
|
33
|
+
s.add_dependency 'sinatra-logger', '>= 0.1.1'
|
34
|
+
s.add_dependency 'activesupport', '>= 3.0.9'
|
35
|
+
|
36
|
+
s.add_development_dependency 'rspec', '>= 2.6.0'
|
37
|
+
s.add_development_dependency 'rack-test', '>= 0.6.0'
|
38
|
+
s.add_development_dependency 'spork', '>= 0.9.0.rc8'
|
39
|
+
s.add_development_dependency 'watchr', '>= 0.7'
|
40
|
+
s.add_development_dependency 'factory_girl', '>= 1.3.3'
|
41
|
+
s.add_development_dependency 'json_spec', '>= 0.5.0'
|
42
|
+
s.add_development_dependency 'timecop', '>= 0.3.5'
|
43
|
+
end
|
File without changes
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Overwatch
|
4
|
+
module Collection
|
5
|
+
describe Attributes do
|
6
|
+
let(:resource) { Overwatch::Resource.create(:name => 'foo') }
|
7
|
+
let(:start_at) { (Time.now - 1.hour).to_i * 1000 }
|
8
|
+
let(:end_at) { Time.now.to_i * 1000 }
|
9
|
+
|
10
|
+
before do
|
11
|
+
resource.snapshots.create(:data => snapshot_data)
|
12
|
+
time_travel!
|
13
|
+
resource.snapshots.create(:data => snapshot_data)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#attribute_keys" do
|
17
|
+
it "should return all available attributes" do
|
18
|
+
resource.attribute_keys.should have(2).items
|
19
|
+
resource.attribute_keys.should include("load_average.one_minute")
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#values_for" do
|
25
|
+
before do
|
26
|
+
time_travel!(Time.now + 1.hour)
|
27
|
+
resource.snapshots.create(:data => snapshot_data)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return all values by default" do
|
31
|
+
values = resource.values_for("load_average.one_minute")
|
32
|
+
values[:data].should have(3).items
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return values within a given date range" do
|
36
|
+
values = resource.values_for("load_average.one_minute",
|
37
|
+
:start_at => start_at, :end_at => end_at
|
38
|
+
)
|
39
|
+
values[:data].should have(2).items
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#values_with_dates_for" do
|
44
|
+
before do
|
45
|
+
time_travel!(Time.now + 1.hour)
|
46
|
+
resource.snapshots.create(:data => snapshot_data)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return all values by default" do
|
50
|
+
values = resource.values_with_dates_for("load_average.one_minute")
|
51
|
+
values[:data].should have(3).items
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return values within a given date range" do
|
55
|
+
values = resource.values_for("load_average.one_minute",
|
56
|
+
:start_at => start_at, :end_at => end_at
|
57
|
+
)
|
58
|
+
values[:data].should have(2).items
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
File without changes
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Overwatch
|
4
|
+
describe Resource do
|
5
|
+
|
6
|
+
describe "new record" do
|
7
|
+
|
8
|
+
it "generates an api key" do
|
9
|
+
resource = Overwatch::Resource.create(:name => "foo")
|
10
|
+
resource.api_key.should_not be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it "creates a new resource" do
|
14
|
+
resource = Overwatch::Resource.create(:name => "foo")
|
15
|
+
resource.should be_valid
|
16
|
+
resource.name.should == "foo"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "disallows duplicate names" do
|
20
|
+
resource = Overwatch::Resource.create(:name => "foo")
|
21
|
+
resource2 = Overwatch::Resource.create(:name => "foo")
|
22
|
+
resource2.should_not be_valid
|
23
|
+
resource2.errors[:name].should == ["Name is already taken"]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "requires a name" do
|
27
|
+
resource = Overwatch::Resource.create
|
28
|
+
resource.should_not be_valid
|
29
|
+
resource.errors[:name].should == ["Name must not be blank"]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#generate_api_key' do
|
35
|
+
it "generates an api key" do
|
36
|
+
resource = Overwatch::Resource.create(:name => "foo")
|
37
|
+
resource.api_key.should_not be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'disallows duplicate api keys' do
|
41
|
+
resource = Overwatch::Resource.create(:name => "foo")
|
42
|
+
resource2 = Overwatch::Resource.create(:name => "bar")
|
43
|
+
resource.api_key = '1234asdf' && resource2.api_key = '1234asdf'
|
44
|
+
resource.save && resource2.save
|
45
|
+
resource2.should_not be_valid
|
46
|
+
resource2.errors[:api_key].should == ["Api key is already taken"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#regenerate_api_key' do
|
51
|
+
it "regenerates an api key" do
|
52
|
+
resource = Overwatch::Resource.create(:name => 'foo')
|
53
|
+
old_api_key = resource.api_key
|
54
|
+
resource.regenerate_api_key
|
55
|
+
resource.api_key.should_not == old_api_key
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|