overwatch-collection 0.1.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.
@@ -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
@@ -0,0 +1,5 @@
1
+ module Overwatch
2
+ module Collection
3
+ VERSION = "0.1.1"
4
+ end
5
+ end
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
@@ -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
@@ -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