khronos 0.0.3.pre4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +129 -0
- data/README.md +47 -1
- data/examples/runner_server.ru +17 -0
- data/examples/scheduler_server.ru +8 -0
- data/khronos.gemspec +4 -4
- data/lib/khronos/scheduler.rb +1 -1
- data/lib/khronos/server/runner.rb +25 -16
- data/lib/khronos/server/scheduler.rb +33 -24
- data/lib/khronos/storage/adapter/activerecord/migrations/schedule.rb +1 -1
- data/lib/khronos/storage/adapter/activerecord/migrations/schedule_log.rb +2 -1
- data/lib/khronos/storage/adapter/activerecord/schedule_log.rb +1 -1
- data/lib/khronos/storage/adapter/mongoid/schedule_log.rb +2 -1
- data/lib/khronos/version.rb +1 -1
- data/spec/functional/scheduler_spec.rb +1 -1
- data/spec/integration/runner_server_spec.rb +29 -12
- data/spec/integration/scheduler_server_spec.rb +114 -51
- data/spec/support/mocks.rb +0 -23
- data/spec/tmp/scheduler.db +0 -0
- data/spec/tmp/sqlite3.db +0 -0
- metadata +12 -11
- data/lib/khronos/server/em_runner.rb +0 -56
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
khronos (0.1.0)
|
5
|
+
activerecord (~> 3.2.8)
|
6
|
+
activesupport (~> 3.2.8)
|
7
|
+
bson_ext (~> 1.6.4)
|
8
|
+
em-http-request (~> 1.0.3)
|
9
|
+
eventmachine (~> 1.0.0.beta.4)
|
10
|
+
girl_friday (~> 0.10.0)
|
11
|
+
json (~> 1.7.5)
|
12
|
+
mongoid (~> 3.0.5)
|
13
|
+
rest-client (~> 1.6.7)
|
14
|
+
sinatra (~> 1.3.3)
|
15
|
+
|
16
|
+
GEM
|
17
|
+
remote: http://rubygems.org/
|
18
|
+
specs:
|
19
|
+
activemodel (3.2.8)
|
20
|
+
activesupport (= 3.2.8)
|
21
|
+
builder (~> 3.0.0)
|
22
|
+
activerecord (3.2.8)
|
23
|
+
activemodel (= 3.2.8)
|
24
|
+
activesupport (= 3.2.8)
|
25
|
+
arel (~> 3.0.2)
|
26
|
+
tzinfo (~> 0.3.29)
|
27
|
+
activesupport (3.2.8)
|
28
|
+
i18n (~> 0.6)
|
29
|
+
multi_json (~> 1.0)
|
30
|
+
addressable (2.3.2)
|
31
|
+
arel (3.0.2)
|
32
|
+
bson (1.6.4)
|
33
|
+
bson_ext (1.6.4)
|
34
|
+
bson (~> 1.6.4)
|
35
|
+
builder (3.0.3)
|
36
|
+
chronic (0.7.0)
|
37
|
+
connection_pool (0.9.2)
|
38
|
+
cookiejar (0.3.0)
|
39
|
+
crack (0.3.1)
|
40
|
+
daemons (1.1.8)
|
41
|
+
database_cleaner (0.8.0)
|
42
|
+
delorean (2.0.0)
|
43
|
+
chronic
|
44
|
+
diff-lcs (1.1.3)
|
45
|
+
em-http-request (1.0.3)
|
46
|
+
addressable (>= 2.2.3)
|
47
|
+
cookiejar
|
48
|
+
em-socksify
|
49
|
+
eventmachine (>= 1.0.0.beta.4)
|
50
|
+
http_parser.rb (>= 0.5.3)
|
51
|
+
em-socksify (0.2.1)
|
52
|
+
eventmachine (>= 1.0.0.beta.4)
|
53
|
+
eventmachine (1.0.0.rc.4)
|
54
|
+
factory_girl (4.0.0)
|
55
|
+
activesupport (>= 3.0.0)
|
56
|
+
fakefs (0.4.0)
|
57
|
+
girl_friday (0.10.0)
|
58
|
+
connection_pool (~> 0.9.0)
|
59
|
+
http_parser.rb (0.5.3)
|
60
|
+
i18n (0.6.1)
|
61
|
+
json (1.7.5)
|
62
|
+
mime-types (1.19)
|
63
|
+
mongo (1.6.4)
|
64
|
+
bson (~> 1.6.4)
|
65
|
+
mongoid (3.0.5)
|
66
|
+
activemodel (~> 3.1)
|
67
|
+
moped (~> 1.1)
|
68
|
+
origin (~> 1.0)
|
69
|
+
tzinfo (~> 0.3.22)
|
70
|
+
moped (1.2.1)
|
71
|
+
multi_json (1.3.6)
|
72
|
+
mysql2 (0.3.11)
|
73
|
+
netrc (0.7.7)
|
74
|
+
origin (1.0.8)
|
75
|
+
pg (0.14.1)
|
76
|
+
rack (1.4.1)
|
77
|
+
rack-protection (1.2.0)
|
78
|
+
rack
|
79
|
+
rack-test (0.6.1)
|
80
|
+
rack (>= 1.0)
|
81
|
+
rest-client (1.6.8)
|
82
|
+
mime-types (>= 1.16)
|
83
|
+
netrc
|
84
|
+
rspec (2.11.0)
|
85
|
+
rspec-core (~> 2.11.0)
|
86
|
+
rspec-expectations (~> 2.11.0)
|
87
|
+
rspec-mocks (~> 2.11.0)
|
88
|
+
rspec-core (2.11.1)
|
89
|
+
rspec-expectations (2.11.2)
|
90
|
+
diff-lcs (~> 1.1.3)
|
91
|
+
rspec-mocks (2.11.2)
|
92
|
+
simplecov (0.6.4)
|
93
|
+
multi_json (~> 1.0)
|
94
|
+
simplecov-html (~> 0.5.3)
|
95
|
+
simplecov-html (0.5.3)
|
96
|
+
sinatra (1.3.3)
|
97
|
+
rack (~> 1.3, >= 1.3.6)
|
98
|
+
rack-protection (~> 1.2)
|
99
|
+
tilt (~> 1.3, >= 1.3.3)
|
100
|
+
sqlite3 (1.3.6)
|
101
|
+
thin (1.4.1)
|
102
|
+
daemons (>= 1.0.9)
|
103
|
+
eventmachine (>= 0.12.6)
|
104
|
+
rack (>= 1.0.0)
|
105
|
+
tilt (1.3.3)
|
106
|
+
tzinfo (0.3.33)
|
107
|
+
webmock (1.8.9)
|
108
|
+
addressable (>= 2.2.7)
|
109
|
+
crack (>= 0.1.7)
|
110
|
+
|
111
|
+
PLATFORMS
|
112
|
+
ruby
|
113
|
+
|
114
|
+
DEPENDENCIES
|
115
|
+
activesupport (~> 3.2.8)
|
116
|
+
database_cleaner
|
117
|
+
delorean (~> 2.0.0)
|
118
|
+
factory_girl (~> 4.0.0)
|
119
|
+
fakefs (~> 0.4.0)
|
120
|
+
khronos!
|
121
|
+
mongo
|
122
|
+
mysql2 (~> 0.3.11)
|
123
|
+
pg
|
124
|
+
rack-test (~> 0.6.1)
|
125
|
+
rspec (~> 2.11.0)
|
126
|
+
simplecov (= 0.6.4)
|
127
|
+
sqlite3
|
128
|
+
thin
|
129
|
+
webmock (~> 1.8.9)
|
data/README.md
CHANGED
@@ -1,8 +1,54 @@
|
|
1
1
|
khronos
|
2
2
|
===
|
3
3
|
|
4
|
-
Job scheduling for the cloud.
|
4
|
+
Simple HTTP-based Job scheduling for the cloud.
|
5
5
|
|
6
|
+
Features
|
7
|
+
---
|
8
|
+
|
9
|
+
- Schedule of HTTP Requests
|
10
|
+
- Configure recurrency per request
|
11
|
+
- Log HTTP status code for every request made
|
12
|
+
- Query the database via REST API
|
13
|
+
- Postgresql, MySQL and SQLite supported. (mongodb will be supported soon)
|
14
|
+
|
15
|
+
How it works
|
16
|
+
---
|
17
|
+
|
18
|
+
Khronos use a rack app to schedule and query for scheduled tasks, and a worker
|
19
|
+
process to execute them in the background.
|
20
|
+
|
21
|
+
At 'examples' directory you find two rackup files for these processes.
|
22
|
+
|
23
|
+
How to use
|
24
|
+
---
|
25
|
+
|
26
|
+
Create a scheduled task:
|
27
|
+
|
28
|
+
RestClient.post('http://localhost:3000/task', {
|
29
|
+
:context => 'test',
|
30
|
+
:at => 24.hours.from_now,
|
31
|
+
:task_url => "http://myapp.com/do-something-awesome",
|
32
|
+
:recurrency => 12.hours
|
33
|
+
})
|
34
|
+
# => "{\"active\":true,\"at\":\"2012-09-15T21:24:56-03:00\",\"context\":\"test\",\"id\":1,\"recurrency\":1,\"task_url\":\"http://myapp.com/do-something-awesome\"}"
|
35
|
+
|
36
|
+
Query for a scheduled task:
|
37
|
+
|
38
|
+
RestClient.get('http://localhost:3000/task', :params => { :context => 'test' })
|
39
|
+
# => "{\"active\":true,\"at\":\"2012-09-15T21:24:56-03:00\",\"context\":\"test\",\"id\":1,\"recurrency\":1,\"task_url\":\"http://myapp.com/do-something-awesome\"}"
|
40
|
+
|
41
|
+
Query for logs for tasks that already ran.
|
42
|
+
|
43
|
+
RestClient.get('http://localhost:3000/schedule/logs', :params => { :status_code => 500 })
|
44
|
+
# => "[{\"id\":3,\"schedule_id\":1,\"started_at\":\"2012-09-15T13:38:48-03:00\",\"status_code\":500},{\"id\":5,\"schedule_id\":2,\"started_at\":\"2012-09-15T13:38:48-03:00\",\"status_code\":500}]"
|
45
|
+
|
46
|
+
Note: these examples are using [rest-client](https://github.com/archiloque/rest-client/) and [activesupport](https://github.com/rails/rails/tree/master/activesupport).
|
47
|
+
|
48
|
+
Contributing
|
49
|
+
---
|
50
|
+
|
51
|
+
Feel free to fork and send pull requests with features and/or bug fixes.
|
6
52
|
|
7
53
|
License
|
8
54
|
---
|
@@ -0,0 +1,17 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'production'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'khronos'
|
5
|
+
require 'khronos/version'
|
6
|
+
|
7
|
+
require 'girl_friday'
|
8
|
+
require 'redis'
|
9
|
+
require 'connection_pool'
|
10
|
+
|
11
|
+
ENV['REDISTOGO_URL'] ||= "redis://127.0.0.1:6379/0"
|
12
|
+
|
13
|
+
$redis = ConnectionPool::Wrapper.new(:size => 2, :timeout => 3) { Redis.connect(:url => ENV["REDISTOGO_URL"]) }
|
14
|
+
runner = Khronos::Server::Runner.new(:runner, :store => GirlFriday::Store::Redis, :store_config => { :pool => $redis })
|
15
|
+
|
16
|
+
Khronos::Config.instance.load!('config/environment.yml', ENV['RACK_ENV'])
|
17
|
+
Khronos::Server::Controller.new(runner).start!
|
data/khronos.gemspec
CHANGED
@@ -7,11 +7,11 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Khronos::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Endel Dreyer"]
|
10
|
-
s.email = ["endel@
|
11
|
-
s.homepage = "http://github.com/
|
10
|
+
s.email = ["endel.dreyer@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/endel/khronos"
|
12
12
|
|
13
|
-
s.summary = "
|
14
|
-
s.description = "
|
13
|
+
s.summary = "Simple HTTP-based Job scheduling for the cloud."
|
14
|
+
s.description = "Simple HTTP-based Job scheduling for the cloud."
|
15
15
|
s.licenses = ['MIT']
|
16
16
|
|
17
17
|
s.add_dependency "sinatra", "~> 1.3.3"
|
data/lib/khronos/scheduler.rb
CHANGED
@@ -16,32 +16,41 @@ module Khronos
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def enqueue(schedule)
|
19
|
-
puts "Khronos::Server::Runner#enqueue => #{schedule.inspect}"
|
20
19
|
@queue.push(schedule.to_json)
|
21
20
|
end
|
22
21
|
|
23
22
|
def process(json)
|
24
23
|
schedule = JSON.parse(json)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
24
|
+
schedule_log = { :started_at => Time.now, :schedule_id => schedule['id'] }
|
25
|
+
|
26
|
+
response = RestClient.get(schedule['task_url'])
|
27
|
+
schedule_log[:status_code] = response.code
|
28
|
+
|
29
|
+
rescue RestClient::Exception => e
|
30
|
+
schedule_log[:status_code] = e.http_code
|
31
|
+
|
32
|
+
ensure
|
33
|
+
log_schedule!(schedule_log)
|
34
|
+
calculate_recurrency!(schedule) if schedule['recurrency'].to_i > 0
|
36
35
|
end
|
37
36
|
|
38
37
|
def calculate_recurrency!(schedule)
|
39
|
-
|
40
|
-
url += ":#{Config.instance.scheduler['port']}" if Config.instance.scheduler['port']
|
41
|
-
url += "/task"
|
42
|
-
RestClient.put(url, :id => schedule['id'], :patch => true)
|
38
|
+
RestClient.put(scheduler_route('/task'), :id => schedule['id'], :patch => true)
|
43
39
|
end
|
44
40
|
|
41
|
+
def log_schedule!(schedule_log)
|
42
|
+
puts "Log schedule! #{schedule_log.inspect}"
|
43
|
+
RestClient.post( scheduler_route('/schedule/log'), schedule_log )
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def scheduler_route(route)
|
49
|
+
url = "http://#{Config.instance.scheduler['host']}"
|
50
|
+
url += ":#{Config.instance.scheduler['port']}" if Config.instance.scheduler['port']
|
51
|
+
url += route
|
52
|
+
end
|
53
|
+
|
45
54
|
end
|
46
55
|
|
47
56
|
end
|
@@ -5,23 +5,16 @@ module Khronos
|
|
5
5
|
|
6
6
|
class Scheduler < Sinatra::Base
|
7
7
|
set :storage, Storage.new
|
8
|
+
before { content_type 'application/json' }
|
8
9
|
|
9
|
-
#
|
10
|
+
# Greetings
|
11
|
+
#
|
10
12
|
get '/' do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
<body>
|
17
|
-
<h1>HTTP Job Scheduler Interface.</h1>
|
18
|
-
<p>
|
19
|
-
<a href="http://rubygems.org/gems/khronos">Khronos #{Khronos::VERSION}</a><br />
|
20
|
-
by <a href="https://github.com/endel">Endel Dreyer</a>
|
21
|
-
</p>
|
22
|
-
</body>
|
23
|
-
</html>
|
24
|
-
EOF
|
13
|
+
{
|
14
|
+
:name => "Khronos - HTTP Job Scheduler Interface.",
|
15
|
+
:version => Khronos::VERSION,
|
16
|
+
:link => "https://github.com/endel/khronos"
|
17
|
+
}
|
25
18
|
end
|
26
19
|
|
27
20
|
# Creates a schedule
|
@@ -97,14 +90,6 @@ module Khronos
|
|
97
90
|
schedule.to_json
|
98
91
|
end
|
99
92
|
|
100
|
-
# Checks recurrency and reactivates a schedule if necessary
|
101
|
-
#
|
102
|
-
# @param [String] id
|
103
|
-
#
|
104
|
-
# @return [Hash] data
|
105
|
-
patch '/task' do
|
106
|
-
end
|
107
|
-
|
108
93
|
# Force a task to be scheduled right now
|
109
94
|
#
|
110
95
|
# @param [Integer] id
|
@@ -116,7 +101,31 @@ module Khronos
|
|
116
101
|
{:queued => !schedule.nil?}.to_json
|
117
102
|
end
|
118
103
|
|
119
|
-
#
|
104
|
+
# Schedule logs querying interface
|
105
|
+
#
|
106
|
+
# @param [Integer] started_at
|
107
|
+
# @param [Integer] schedule_id
|
108
|
+
# @param [Integer] status_code
|
109
|
+
#
|
110
|
+
# @return [Array] list of schedule logs
|
111
|
+
#
|
112
|
+
get '/schedule/logs' do
|
113
|
+
limit = params.delete('limit')
|
114
|
+
offset = params.delete('offset')
|
115
|
+
|
116
|
+
relation = Storage::ScheduleLog.where(params)
|
117
|
+
relation = relation.limit(limit) if limit
|
118
|
+
relation = relation.offset(offset) if offset
|
119
|
+
relation.to_json
|
120
|
+
end
|
121
|
+
|
122
|
+
# Create a schedule log
|
123
|
+
#
|
124
|
+
# @param [Integer] started_at
|
125
|
+
# @param [Integer] status_code
|
126
|
+
post '/schedule/log' do
|
127
|
+
Storage::ScheduleLog.create(params).to_json
|
128
|
+
end
|
120
129
|
|
121
130
|
end
|
122
131
|
|
@@ -9,7 +9,7 @@ module Khronos
|
|
9
9
|
t.string :context, :null => false, :limit => 100
|
10
10
|
t.datetime :at, :null => false
|
11
11
|
t.string :task_url, :null => false
|
12
|
-
t.integer :recurrency, :null =>
|
12
|
+
t.integer :recurrency, :null => true, :default => 0
|
13
13
|
t.string :callbacks, :null => true, :limit => 500
|
14
14
|
t.boolean :active, :null => false, :default => true
|
15
15
|
end
|
data/lib/khronos/version.rb
CHANGED
@@ -2,25 +2,42 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Khronos::Server::Runner do
|
4
4
|
subject { Khronos::Server::Runner }
|
5
|
-
let(:recurrency_check_url) { "http://localhost:8080/task
|
6
|
-
let(:
|
5
|
+
let(:recurrency_check_url) { "http://localhost:8080/task" }
|
6
|
+
let(:schedule_log_url) { "http://localhost:8080/schedule/log" }
|
7
|
+
let(:valid_task_url) { 'http://test.com' }
|
8
|
+
let(:invalid_task_url) { 'http://test.com/404' }
|
7
9
|
|
8
10
|
before(:each) do
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
stub_request(:get, task_url).to_return(:body => 'it works!', :status => 200)
|
14
|
-
stub_request(:patch, recurrency_check_url).to_return(:status => 200, :body => "", :headers => {})
|
11
|
+
stub_request(:get, valid_task_url).to_return(:body => 'it works!', :status => 200)
|
12
|
+
stub_request(:get, invalid_task_url).to_return(:status => 404)
|
13
|
+
stub_request(:put, recurrency_check_url).with(:id => 1, :patch => true).to_return(:status => 200, :body => "", :headers => {})
|
15
14
|
end
|
16
15
|
|
17
16
|
it "should run a task" do
|
17
|
+
stub_request(:post, schedule_log_url).with(:schedule_id => 1).to_return(:status => 200, :body => "", :headers => {})
|
18
|
+
stub_request(:post, schedule_log_url).with(:schedule_id => 2, :status_code => 404).to_return(:status => 200, :body => "", :headers => {})
|
19
|
+
|
18
20
|
runner = subject.new(nil)
|
19
|
-
|
20
|
-
|
21
|
+
runner.process({:id => 1, :task_url => valid_task_url, :recurrency => 0}.to_json)
|
22
|
+
runner.process({:id => 2, :task_url => invalid_task_url, :recurrency => 60}.to_json)
|
23
|
+
|
24
|
+
a_request(:get, valid_task_url).should have_been_made
|
25
|
+
a_request(:get, invalid_task_url).should have_been_made
|
26
|
+
a_request(:post, schedule_log_url).with {|r| r.body =~ /schedule_id=2/ && r.body =~ /status_code=404/ }.should have_been_made
|
27
|
+
a_request(:post, schedule_log_url).with {|r| r.body =~ /schedule_id=1/ && r.body =~ /status_code=200/ }.should have_been_made
|
28
|
+
a_request(:put, recurrency_check_url).should have_been_made
|
29
|
+
end
|
30
|
+
|
31
|
+
context "callbacks" do
|
32
|
+
|
33
|
+
xit "should trigger error callback" do
|
34
|
+
# not implemented yet
|
35
|
+
end
|
36
|
+
|
37
|
+
xit "should trigger success callback" do
|
38
|
+
# not implemented yet
|
21
39
|
end
|
22
|
-
|
23
|
-
a_request(:patch, recurrency_check_url).should have_been_made
|
40
|
+
|
24
41
|
end
|
25
42
|
|
26
43
|
end
|
@@ -7,64 +7,127 @@ describe Khronos::Server::Scheduler do
|
|
7
7
|
Khronos::Server::Scheduler
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
context "tasks" do
|
11
|
+
it "should return 404 for invalid task requests" do
|
12
|
+
get('/task')
|
13
|
+
last_response.status.should == 404
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
get('/task', {:context => "invalid"})
|
16
|
+
last_response.status.should == 404
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
get('/task', {:id => 99})
|
19
|
+
last_response.status.should == 404
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
22
|
+
it "should schedule context to 1 week" do
|
23
|
+
post('/task', {
|
24
|
+
:context => "1-week-test",
|
25
|
+
:schedule => 1.week,
|
26
|
+
:at => Time.now,
|
27
|
+
:recurrency => 1.week,
|
28
|
+
:task_url => "http://fake"
|
29
|
+
})
|
30
|
+
last_response.status.should == 200
|
31
|
+
Khronos::Storage::Schedule.where(:context => "1-week-test").count.should == 1
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
post('/task', {
|
34
|
+
:context => "2-weeks-test",
|
35
|
+
:schedule => 2.weeks,
|
36
|
+
:at => Time.now,
|
37
|
+
:task_url => "http://fake"
|
38
|
+
})
|
39
|
+
last_response.status.should == 200
|
40
|
+
Khronos::Storage::Schedule.where(:context => "2-weeks-test").count.should == 1
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should enqueue schedule to run immediatelly" do
|
44
|
+
dummy_schedule = FactoryGirl.create(:schedule)
|
45
|
+
post('/task/run', :id => dummy_schedule.id)
|
46
|
+
last_response.status.should == 200
|
47
|
+
JSON.parse(last_response.body).should == {'queued' => true}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should update schedule data" do
|
51
|
+
dummy_schedule = FactoryGirl.create(:schedule, {
|
52
|
+
:context => "will be updated",
|
53
|
+
:at => Time.now,
|
54
|
+
:active => true,
|
55
|
+
:recurrency => 0
|
56
|
+
})
|
57
|
+
request('/task', :method => 'PUT', :params => {:id => dummy_schedule.id, :context => "updated"})
|
58
|
+
schedule = Khronos::Storage::Schedule.find(dummy_schedule.id)
|
59
|
+
schedule.active.should == true
|
60
|
+
schedule.context.should == 'updated'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should update recurring time" do
|
64
|
+
dummy_schedule = FactoryGirl.create(:schedule, {
|
65
|
+
:at => Time.now,
|
66
|
+
:active => false,
|
67
|
+
:recurrency => 1.day
|
68
|
+
})
|
69
|
+
request('/task', :method => 'PUT', :params => {:id => dummy_schedule.id, :patch => true})
|
70
|
+
schedule = Khronos::Storage::Schedule.find(dummy_schedule.id)
|
71
|
+
schedule.active.should == true
|
72
|
+
schedule.at.to_i.should == 1.day.from_now.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should retrieve a list of schedules by context pattern" do
|
76
|
+
FactoryGirl.create(:schedule, {:context => "namespaced"})
|
77
|
+
FactoryGirl.create(:schedule, {:context => "namespaced:1"})
|
78
|
+
FactoryGirl.create(:schedule, {:context => "namespaced:2"})
|
79
|
+
FactoryGirl.create(:schedule, {:context => "namespaced:3"})
|
80
|
+
FactoryGirl.create(:schedule, {:context => "namespaced:4"})
|
81
|
+
FactoryGirl.create(:schedule, {:context => "namespaced:5"})
|
40
82
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
schedule.active.should == true
|
50
|
-
schedule.at.to_i.should == 1.day.from_now.to_i
|
83
|
+
get('/tasks', {:context => "namespaced%"})
|
84
|
+
last_response.status.should == 200
|
85
|
+
JSON.parse(last_response.body).length.should == 6
|
86
|
+
|
87
|
+
get('/tasks', {:context => "namespaced"})
|
88
|
+
last_response.status.should == 200
|
89
|
+
JSON.parse(last_response.body).length.should == 1
|
90
|
+
end
|
51
91
|
end
|
52
92
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
93
|
+
context "logs" do
|
94
|
+
it "should record schedule logs" do
|
95
|
+
data = { :schedule_id => 1, :started_at => Time.at(1347727128), :status_code => 404 }
|
96
|
+
post('/schedule/log', data)
|
97
|
+
last_response.status.should == 200
|
98
|
+
|
99
|
+
schedule_log = Khronos::Storage::ScheduleLog.find(JSON.parse(last_response.body)['id'])
|
100
|
+
schedule_log.schedule_id.should == data[:schedule_id]
|
101
|
+
schedule_log.started_at.should == data[:started_at]
|
102
|
+
schedule_log.status_code.should == data[:status_code]
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should query for schedule logs" do
|
106
|
+
FactoryGirl.create(:schedule_log, {:started_at => Time.at(1347727128), :status_code => 200, :schedule_id => 1})
|
107
|
+
FactoryGirl.create(:schedule_log, {:started_at => Time.at(1347727128), :status_code => 500, :schedule_id => 1})
|
108
|
+
FactoryGirl.create(:schedule_log, {:started_at => Time.at(1347727128), :status_code => 200, :schedule_id => 1})
|
109
|
+
FactoryGirl.create(:schedule_log, {:started_at => Time.at(1347727128), :status_code => 500, :schedule_id => 2})
|
110
|
+
FactoryGirl.create(:schedule_log, {:started_at => Time.at(1347727128), :status_code => 200, :schedule_id => 2})
|
111
|
+
FactoryGirl.create(:schedule_log, {:started_at => Time.at(1347727128), :status_code => 404, :schedule_id => 2})
|
112
|
+
FactoryGirl.create(:schedule_log, {:started_at => Time.at(1347727128), :status_code => 200, :schedule_id => 3})
|
113
|
+
|
114
|
+
get('/schedule/logs', {:status_code => 200})
|
115
|
+
last_response.status.should == 200
|
116
|
+
JSON.parse(last_response.body).length.should == 4
|
117
|
+
|
118
|
+
get('/schedule/logs', {:schedule_id => 2})
|
119
|
+
last_response.status.should == 200
|
120
|
+
logs = JSON.parse(last_response.body)
|
121
|
+
logs.length.should == 3
|
122
|
+
|
123
|
+
get('/schedule/logs', {:status_code => 200, :limit => 1})
|
124
|
+
JSON.parse(last_response.body).length.should == 1
|
125
|
+
|
126
|
+
get('/schedule/logs', {:status_code => 500})
|
127
|
+
puts "status_code => #{last_response.body.inspect}"
|
128
|
+
#logs = JSON.parse(last_response.body)
|
129
|
+
#logs.first['schedule_id'].should == 2
|
130
|
+
end
|
68
131
|
end
|
69
132
|
|
70
133
|
end
|
data/spec/support/mocks.rb
CHANGED
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
TCP_NEW = TCPSocket.method(:new) unless defined? TCP_NEW
|
3
|
-
|
4
|
-
#
|
5
|
-
# Example:
|
6
|
-
# mock_tcp_next_request("<xml>junk</xml>")
|
7
|
-
#
|
8
|
-
class FakeTCPSocket
|
9
|
-
def puts(*args); end
|
10
|
-
def closed?; true; end
|
11
|
-
def write(some_text = nil); end
|
12
|
-
end
|
13
|
-
|
14
|
-
def mock_tcp_next_request(string)
|
15
|
-
TCPSocket.stub!(:new).and_return {
|
16
|
-
cm = FakeTCPSocket.new
|
17
|
-
cm
|
18
|
-
}
|
19
|
-
end
|
20
|
-
|
21
|
-
def unmock_tcp
|
22
|
-
TCPSocket.stub!(:new).and_return { TCP_NEW.call }
|
23
|
-
end
|
data/spec/tmp/scheduler.db
CHANGED
Binary file
|
data/spec/tmp/sqlite3.db
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: khronos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Endel Dreyer
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sinatra
|
@@ -171,10 +171,9 @@ dependencies:
|
|
171
171
|
- - ~>
|
172
172
|
- !ruby/object:Gem::Version
|
173
173
|
version: 0.10.0
|
174
|
-
description:
|
175
|
-
cloud.
|
174
|
+
description: Simple HTTP-based Job scheduling for the cloud.
|
176
175
|
email:
|
177
|
-
- endel@
|
176
|
+
- endel.dreyer@gmail.com
|
178
177
|
executables: []
|
179
178
|
extensions: []
|
180
179
|
extra_rdoc_files: []
|
@@ -183,6 +182,7 @@ files:
|
|
183
182
|
- .rspec
|
184
183
|
- .travis.yml
|
185
184
|
- Gemfile
|
185
|
+
- Gemfile.lock
|
186
186
|
- LICENSE
|
187
187
|
- README.md
|
188
188
|
- Rakefile
|
@@ -190,6 +190,8 @@ files:
|
|
190
190
|
- config.ru
|
191
191
|
- config/environment.yml
|
192
192
|
- db/test.db
|
193
|
+
- examples/runner_server.ru
|
194
|
+
- examples/scheduler_server.ru
|
193
195
|
- khronos.gemspec
|
194
196
|
- lib/.scheduler.rb.swp
|
195
197
|
- lib/khronos.rb
|
@@ -198,7 +200,6 @@ files:
|
|
198
200
|
- lib/khronos/scheduler.rb
|
199
201
|
- lib/khronos/server.rb
|
200
202
|
- lib/khronos/server/controller.rb
|
201
|
-
- lib/khronos/server/em_runner.rb
|
202
203
|
- lib/khronos/server/runner.rb
|
203
204
|
- lib/khronos/server/scheduler.rb
|
204
205
|
- lib/khronos/storage.rb
|
@@ -228,7 +229,7 @@ files:
|
|
228
229
|
- spec/support/mocks.rb
|
229
230
|
- spec/tmp/scheduler.db
|
230
231
|
- spec/tmp/sqlite3.db
|
231
|
-
homepage: http://github.com/
|
232
|
+
homepage: http://github.com/endel/khronos
|
232
233
|
licenses:
|
233
234
|
- MIT
|
234
235
|
post_install_message:
|
@@ -244,15 +245,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
244
245
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
245
246
|
none: false
|
246
247
|
requirements:
|
247
|
-
- - ! '
|
248
|
+
- - ! '>='
|
248
249
|
- !ruby/object:Gem::Version
|
249
|
-
version:
|
250
|
+
version: '0'
|
250
251
|
requirements: []
|
251
252
|
rubyforge_project:
|
252
253
|
rubygems_version: 1.8.24
|
253
254
|
signing_key:
|
254
255
|
specification_version: 3
|
255
|
-
summary:
|
256
|
+
summary: Simple HTTP-based Job scheduling for the cloud.
|
256
257
|
test_files:
|
257
258
|
- spec/functional/adapters_spec.rb
|
258
259
|
- spec/functional/controller_spec.rb
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'eventmachine'
|
3
|
-
require 'em-http'
|
4
|
-
|
5
|
-
#
|
6
|
-
# DEPRECATED:
|
7
|
-
# This class was used just for testing purpose.
|
8
|
-
#
|
9
|
-
|
10
|
-
module Khronos
|
11
|
-
module Server
|
12
|
-
|
13
|
-
class Runner < EventMachine::Connection
|
14
|
-
def post_init
|
15
|
-
puts "-- someone connected to the server!"
|
16
|
-
end
|
17
|
-
|
18
|
-
def receive_data json
|
19
|
-
puts "Receive data to run: #{json}"
|
20
|
-
schedule = JSON.parse(json)
|
21
|
-
send_data ">>> you sent: #{schedule.inspect}"
|
22
|
-
|
23
|
-
# Close connection with client immediatelly
|
24
|
-
close_connection
|
25
|
-
|
26
|
-
if (url = schedule['task_url'])
|
27
|
-
http = EventMachine::HttpRequest.new(url).get :redirects => 5
|
28
|
-
http.callback do
|
29
|
-
puts "#{url}\n#{http.response_header.status} - #{http.response.length} bytes\n"
|
30
|
-
puts http.response
|
31
|
-
end
|
32
|
-
|
33
|
-
http.errback do
|
34
|
-
puts "#{url}\n" + http.error
|
35
|
-
end
|
36
|
-
|
37
|
-
enqueue_recurrency!(schedule)
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
def enqueue_recurrency!(schedule)
|
43
|
-
url = "http://#{Config.instance.scheduler['host']}"
|
44
|
-
url += ":#{Config.instance.scheduler['port']}" if Config.instance.scheduler['port']
|
45
|
-
url += "/task?id=#{schedule['id']}"
|
46
|
-
EventMachine::HttpRequest.new(url).patch :redirects => 2
|
47
|
-
end
|
48
|
-
|
49
|
-
def unbind
|
50
|
-
puts "-- someone disconnected from the echo server!"
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
end
|