crono 0.7.0 → 0.8.0
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 +4 -4
- data/Changes.md +19 -8
- data/Gemfile.lock +5 -2
- data/README.md +11 -9
- data/bin/crono +2 -2
- data/crono.gemspec +21 -20
- data/examples/cronotab.rb +3 -4
- data/lib/crono.rb +12 -9
- data/lib/crono/cli.rb +21 -15
- data/lib/crono/config.rb +6 -9
- data/lib/crono/job.rb +49 -25
- data/lib/crono/logging.rb +2 -1
- data/lib/crono/orm/active_record/crono_job.rb +2 -1
- data/lib/crono/performer_proxy.rb +1 -0
- data/lib/crono/period.rb +37 -12
- data/lib/crono/scheduler.rb +3 -1
- data/lib/crono/version.rb +1 -1
- data/lib/crono/web.rb +6 -4
- data/lib/generators/crono/install/install_generator.rb +5 -3
- data/lib/generators/crono/install/templates/cronotab.rb.erb +2 -2
- data/lib/generators/crono/install/templates/migrations/create_crono_jobs.rb +1 -0
- data/spec/cli_spec.rb +20 -23
- data/spec/config_spec.rb +5 -5
- data/spec/job_spec.rb +49 -27
- data/spec/orm/active_record/crono_job_spec.rb +8 -8
- data/spec/performer_proxy_spec.rb +5 -4
- data/spec/period_spec.rb +59 -15
- data/spec/scheduler_spec.rb +11 -10
- data/spec/spec_helper.rb +5 -4
- data/spec/web_spec.rb +48 -0
- data/web/views/dashboard.haml +8 -3
- data/web/views/job.haml +7 -1
- data/web/views/layout.haml +9 -9
- metadata +18 -3
data/lib/crono/logging.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
|
3
3
|
module Crono
|
4
|
+
# Crono::CronoJob is a ActiveRecord model to store job state
|
4
5
|
class CronoJob < ActiveRecord::Base
|
5
|
-
self.table_name =
|
6
|
+
self.table_name = 'crono_jobs'
|
6
7
|
validates :job_id, presence: true, uniqueness: true
|
7
8
|
end
|
8
9
|
end
|
data/lib/crono/period.rb
CHANGED
@@ -1,27 +1,53 @@
|
|
1
1
|
module Crono
|
2
|
+
# Period describe frequency of performing a task
|
2
3
|
class Period
|
3
|
-
|
4
|
+
DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday,
|
5
|
+
:sunday]
|
6
|
+
|
7
|
+
def initialize(period, at: nil, on: nil)
|
4
8
|
@period = period
|
5
9
|
@at_hour, @at_min = parse_at(at) if at
|
10
|
+
@on = parse_on(on) if on
|
6
11
|
end
|
7
12
|
|
8
13
|
def next(since: nil)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
@period.since(since).change(time_atts)
|
14
|
+
return initial_next unless since
|
15
|
+
@next = @period.since(since)
|
16
|
+
@next = @next.beginning_of_week.advance(days: @on) if @on
|
17
|
+
@next.change(time_atts)
|
16
18
|
end
|
17
19
|
|
18
20
|
def description
|
19
21
|
desc = "every #{@period.inspect}"
|
20
|
-
desc +=
|
22
|
+
desc += format(' at %.2i:%.2i', @at_hour, @at_min) if @at_hour && @at_min
|
23
|
+
desc += " on #{DAYS[@on].capitalize}" if @on
|
21
24
|
desc
|
22
25
|
end
|
23
26
|
|
27
|
+
private
|
28
|
+
|
29
|
+
def initial_next
|
30
|
+
next_time = initial_day.change(time_atts)
|
31
|
+
return next_time if next_time.future?
|
32
|
+
@period.from_now.change(time_atts)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initial_day
|
36
|
+
return Time.now unless @on
|
37
|
+
day = Time.now.beginning_of_week.advance(days: @on)
|
38
|
+
return day if day.future?
|
39
|
+
@period.from_now.beginning_of_week.advance(days: @on)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_on(on)
|
43
|
+
day_number = DAYS.index(on)
|
44
|
+
fail "Wrong 'on' day" unless day_number
|
45
|
+
fail "period should be at least 1 week to use 'on'" if @period < 1.week
|
46
|
+
day_number
|
47
|
+
end
|
48
|
+
|
24
49
|
def parse_at(at)
|
50
|
+
fail "period should be at least 1 day to use 'at'" if @period < 1.day
|
25
51
|
case at
|
26
52
|
when String
|
27
53
|
time = Time.parse(at)
|
@@ -29,13 +55,12 @@ module Crono
|
|
29
55
|
when Hash
|
30
56
|
return at[:hour], at[:min]
|
31
57
|
else
|
32
|
-
|
58
|
+
fail "Unknown 'at' format"
|
33
59
|
end
|
34
60
|
end
|
35
61
|
|
36
|
-
private
|
37
62
|
def time_atts
|
38
|
-
{hour: @at_hour, min: @at_min}.compact
|
63
|
+
{ hour: @at_hour, min: @at_min }.compact
|
39
64
|
end
|
40
65
|
end
|
41
66
|
end
|
data/lib/crono/scheduler.rb
CHANGED
data/lib/crono/version.rb
CHANGED
data/lib/crono/web.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
require 'haml'
|
2
2
|
require 'sinatra/base'
|
3
|
+
require 'crono'
|
3
4
|
|
4
5
|
module Crono
|
6
|
+
# Web is a Web UI Sinatra app
|
5
7
|
class Web < Sinatra::Base
|
6
|
-
set :root, File.expand_path(File.dirname(__FILE__) +
|
7
|
-
set :public_folder,
|
8
|
-
set :views,
|
8
|
+
set :root, File.expand_path(File.dirname(__FILE__) + '/../../web')
|
9
|
+
set :public_folder, proc { "#{root}/assets" }
|
10
|
+
set :views, proc { "#{root}/views" }
|
9
11
|
|
10
12
|
get '/' do
|
11
13
|
@jobs = Crono::CronoJob.all
|
12
14
|
haml :dashboard, format: :html5
|
13
15
|
end
|
14
16
|
|
15
|
-
get '/
|
17
|
+
get '/job/:id' do
|
16
18
|
@job = Crono::CronoJob.find(params[:id])
|
17
19
|
haml :job
|
18
20
|
end
|
@@ -4,6 +4,7 @@ require 'rails/generators/active_record'
|
|
4
4
|
|
5
5
|
module Crono
|
6
6
|
module Generators
|
7
|
+
# rails generate crono:install
|
7
8
|
class InstallGenerator < ::Rails::Generators::Base
|
8
9
|
include Rails::Generators::Migration
|
9
10
|
|
@@ -11,15 +12,16 @@ module Crono
|
|
11
12
|
ActiveRecord::Generators::Base.next_migration_number(path)
|
12
13
|
end
|
13
14
|
|
14
|
-
desc
|
15
|
-
source_root File.expand_path(
|
15
|
+
desc 'Installs crono and generates the necessary configuration files'
|
16
|
+
source_root File.expand_path('../templates', __FILE__)
|
16
17
|
|
17
18
|
def copy_config
|
18
19
|
template 'cronotab.rb.erb', 'config/cronotab.rb'
|
19
20
|
end
|
20
21
|
|
21
22
|
def create_migrations
|
22
|
-
migration_template 'migrations/create_crono_jobs.rb',
|
23
|
+
migration_template 'migrations/create_crono_jobs.rb',
|
24
|
+
'db/migrate/create_crono_jobs.rb'
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
data/spec/cli_spec.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'crono/cli'
|
3
3
|
|
4
4
|
class TestJob
|
5
|
-
def perform
|
5
|
+
def perform
|
6
|
+
end
|
6
7
|
end
|
7
8
|
|
8
9
|
describe Crono::CLI do
|
9
10
|
let(:cli) { Crono::CLI.instance }
|
10
11
|
|
11
|
-
describe
|
12
|
-
it
|
12
|
+
describe '#run' do
|
13
|
+
it 'should initialize rails with #load_rails and start working loop' do
|
13
14
|
expect(cli).to receive(:load_rails)
|
14
15
|
expect(cli).to receive(:start_working_loop)
|
15
16
|
expect(cli).to receive(:parse_options)
|
@@ -18,34 +19,30 @@ describe Crono::CLI do
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
describe
|
22
|
-
it
|
23
|
-
|
24
|
-
|
25
|
-
describe "#parse_options" do
|
26
|
-
it "should set cronotab" do
|
27
|
-
cli.send(:parse_options, ["--cronotab", "/tmp/cronotab.rb"])
|
28
|
-
expect(cli.config.cronotab).to be_eql "/tmp/cronotab.rb"
|
22
|
+
describe '#parse_options' do
|
23
|
+
it 'should set cronotab' do
|
24
|
+
cli.send(:parse_options, ['--cronotab', '/tmp/cronotab.rb'])
|
25
|
+
expect(cli.config.cronotab).to be_eql '/tmp/cronotab.rb'
|
29
26
|
end
|
30
27
|
|
31
|
-
it
|
32
|
-
cli.send(:parse_options, [
|
33
|
-
expect(cli.config.logfile).to be_eql
|
28
|
+
it 'should set logfile' do
|
29
|
+
cli.send(:parse_options, ['--logfile', 'log/crono.log'])
|
30
|
+
expect(cli.config.logfile).to be_eql 'log/crono.log'
|
34
31
|
end
|
35
32
|
|
36
|
-
it
|
37
|
-
cli.send(:parse_options, [
|
38
|
-
expect(cli.config.pidfile).to be_eql
|
33
|
+
it 'should set pidfile' do
|
34
|
+
cli.send(:parse_options, ['--pidfile', 'tmp/pids/crono.0.log'])
|
35
|
+
expect(cli.config.pidfile).to be_eql 'tmp/pids/crono.0.log'
|
39
36
|
end
|
40
37
|
|
41
|
-
it
|
42
|
-
cli.send(:parse_options, [
|
38
|
+
it 'should set daemonize' do
|
39
|
+
cli.send(:parse_options, ['--daemonize'])
|
43
40
|
expect(cli.config.daemonize).to be true
|
44
41
|
end
|
45
42
|
|
46
|
-
it
|
47
|
-
cli.send(:parse_options, [
|
48
|
-
expect(cli.config.environment).to be_eql(
|
43
|
+
it 'should set environment' do
|
44
|
+
cli.send(:parse_options, ['--environment', 'production'])
|
45
|
+
expect(cli.config.environment).to be_eql('production')
|
49
46
|
end
|
50
47
|
end
|
51
48
|
end
|
data/spec/config_spec.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Crono::Config do
|
4
|
-
describe
|
5
|
-
it
|
6
|
-
ENV[
|
4
|
+
describe '#initialize' do
|
5
|
+
it 'should initialize with default configuration options' do
|
6
|
+
ENV['RAILS_ENV'] = 'test'
|
7
7
|
@config = Crono::Config.new
|
8
8
|
expect(@config.cronotab).to be Crono::Config::CRONOTAB
|
9
9
|
expect(@config.logfile).to be Crono::Config::LOGFILE
|
10
10
|
expect(@config.pidfile).to be Crono::Config::PIDFILE
|
11
11
|
expect(@config.daemonize).to be false
|
12
|
-
expect(@config.environment).to be_eql ENV[
|
12
|
+
expect(@config.environment).to be_eql ENV['RAILS_ENV']
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
data/spec/job_spec.rb
CHANGED
@@ -1,63 +1,77 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
class TestJob
|
4
|
-
def perform
|
4
|
+
def perform
|
5
|
+
end
|
5
6
|
end
|
6
7
|
|
7
8
|
class TestFailingJob
|
8
9
|
def perform
|
9
|
-
|
10
|
+
fail 'Some error'
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
describe Crono::Job do
|
14
15
|
let(:period) { Crono::Period.new(2.day) }
|
15
|
-
let(:job) { Crono::Job.new(TestJob, period) }
|
16
|
+
let(:job) { Crono::Job.new(TestJob, period) }
|
16
17
|
let(:failing_job) { Crono::Job.new(TestFailingJob, period) }
|
17
18
|
|
18
|
-
it
|
19
|
+
it 'should contain performer and period' do
|
19
20
|
expect(job.performer).to be TestJob
|
20
21
|
expect(job.period).to be period
|
21
22
|
end
|
22
23
|
|
23
|
-
describe
|
24
|
-
|
24
|
+
describe '#perform' do
|
25
|
+
after { job.send(:model).destroy }
|
26
|
+
|
27
|
+
it 'should run performer in separate thread' do
|
25
28
|
expect(job).to receive(:save)
|
26
29
|
thread = job.perform.join
|
27
30
|
expect(thread).to be_stop
|
28
|
-
job.send(:model).destroy
|
29
31
|
end
|
30
32
|
|
31
|
-
it
|
33
|
+
it 'should save performin errors to log' do
|
32
34
|
thread = failing_job.perform.join
|
33
35
|
expect(thread).to be_stop
|
34
36
|
saved_log = Crono::CronoJob.find_by(job_id: failing_job.job_id).log
|
35
|
-
expect(saved_log).to include
|
37
|
+
expect(saved_log).to include 'Some error'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should set Job#healthy to true if perform ok' do
|
41
|
+
job.perform.join
|
42
|
+
expect(job.healthy).to be true
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should set Job#healthy to false if perform with error' do
|
46
|
+
failing_job.perform.join
|
47
|
+
expect(failing_job.healthy).to be false
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
39
|
-
describe
|
40
|
-
it
|
41
|
-
expect(job.description).to be_eql(
|
51
|
+
describe '#description' do
|
52
|
+
it 'should return job identificator' do
|
53
|
+
expect(job.description).to be_eql('Perform TestJob every 2 days')
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
45
|
-
describe
|
46
|
-
it
|
57
|
+
describe '#save' do
|
58
|
+
it 'should save new job to DB' do
|
47
59
|
expect(Crono::CronoJob.where(job_id: job.job_id)).to_not exist
|
48
60
|
job.save
|
49
61
|
expect(Crono::CronoJob.where(job_id: job.job_id)).to exist
|
50
62
|
end
|
51
63
|
|
52
|
-
it
|
64
|
+
it 'should update saved job' do
|
53
65
|
job.last_performed_at = Time.now
|
66
|
+
job.healthy = true
|
54
67
|
job.save
|
55
68
|
@crono_job = Crono::CronoJob.find_by(job_id: job.job_id)
|
56
69
|
expect(@crono_job.last_performed_at.utc.to_s).to be_eql job.last_performed_at.utc.to_s
|
70
|
+
expect(@crono_job.healthy).to be true
|
57
71
|
end
|
58
72
|
|
59
|
-
it
|
60
|
-
message =
|
73
|
+
it 'should save and truncate job log' do
|
74
|
+
message = 'test message'
|
61
75
|
job.send(:log, message)
|
62
76
|
job.save
|
63
77
|
expect(job.send(:model).reload.log).to include message
|
@@ -65,31 +79,39 @@ describe Crono::Job do
|
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
68
|
-
describe
|
82
|
+
describe '#load' do
|
69
83
|
before do
|
70
84
|
@saved_last_performed_at = job.last_performed_at = Time.now
|
71
85
|
job.save
|
72
86
|
end
|
73
87
|
|
74
|
-
it
|
88
|
+
it 'should load last_performed_at from DB' do
|
75
89
|
@job = Crono::Job.new(TestJob, period)
|
76
90
|
@job.load
|
77
91
|
expect(@job.last_performed_at.utc.to_s).to be_eql @saved_last_performed_at.utc.to_s
|
78
92
|
end
|
79
93
|
end
|
80
94
|
|
81
|
-
describe
|
82
|
-
it
|
83
|
-
message =
|
84
|
-
expect(job.logger).to receive(:
|
85
|
-
expect(job.job_logger).to receive(:
|
95
|
+
describe '#log' do
|
96
|
+
it 'should write log messages to both common and job log' do
|
97
|
+
message = 'Test message'
|
98
|
+
expect(job.logger).to receive(:log).with(Logger::INFO, message)
|
99
|
+
expect(job.job_logger).to receive(:log).with(Logger::INFO, message)
|
86
100
|
job.send(:log, message)
|
87
101
|
end
|
88
102
|
|
89
|
-
it
|
90
|
-
message =
|
103
|
+
it 'should write job log to Job#job_log' do
|
104
|
+
message = 'Test message'
|
91
105
|
job.send(:log, message)
|
92
106
|
expect(job.job_log.string).to include(message)
|
93
107
|
end
|
94
108
|
end
|
109
|
+
|
110
|
+
describe '#log_error' do
|
111
|
+
it 'should call log with ERROR severity' do
|
112
|
+
message = 'Test message'
|
113
|
+
expect(job).to receive(:log).with(message, Logger::ERROR)
|
114
|
+
job.send(:log_error, message)
|
115
|
+
end
|
116
|
+
end
|
95
117
|
end
|
@@ -1,26 +1,26 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Crono::CronoJob do
|
4
4
|
let(:valid_attrs) do
|
5
5
|
{
|
6
|
-
job_id:
|
6
|
+
job_id: 'Perform TestJob every 3 days'
|
7
7
|
}
|
8
8
|
end
|
9
9
|
|
10
|
-
it
|
11
|
-
@crono_job = Crono::CronoJob.new
|
10
|
+
it 'should validate presence of job_id' do
|
11
|
+
@crono_job = Crono::CronoJob.new
|
12
12
|
expect(@crono_job).not_to be_valid
|
13
13
|
expect(@crono_job.errors.added?(:job_id, :blank)).to be true
|
14
14
|
end
|
15
15
|
|
16
|
-
it
|
17
|
-
Crono::CronoJob.create!(job_id:
|
18
|
-
@crono_job = Crono::CronoJob.create(job_id:
|
16
|
+
it 'should validate uniqueness of job_id' do
|
17
|
+
Crono::CronoJob.create!(job_id: 'TestJob every 2 days')
|
18
|
+
@crono_job = Crono::CronoJob.create(job_id: 'TestJob every 2 days')
|
19
19
|
expect(@crono_job).not_to be_valid
|
20
20
|
expect(@crono_job.errors.added?(:job_id, :taken)).to be true
|
21
21
|
end
|
22
22
|
|
23
|
-
it
|
23
|
+
it 'should save job_id to DB' do
|
24
24
|
Crono::CronoJob.create!(valid_attrs)
|
25
25
|
@crono_job = Crono::CronoJob.find_by(job_id: valid_attrs[:job_id])
|
26
26
|
expect(@crono_job).to be_present
|