abid 0.3.0.pre.alpha.3 → 0.3.0.pre.alpha.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/abid.gemspec +1 -2
- data/exe/abidsc +1 -1
- data/lib/abid.rb +3 -30
- data/lib/abid/application.rb +83 -13
- data/lib/abid/cli/assume.rb +7 -8
- data/lib/abid/cli/list.rb +2 -2
- data/lib/abid/cli/migrate.rb +1 -1
- data/lib/abid/cli/revoke.rb +9 -10
- data/lib/abid/config.rb +2 -0
- data/lib/abid/dsl/abid_job.rb +58 -0
- data/lib/abid/dsl/actions.rb +36 -0
- data/lib/abid/dsl/job.rb +58 -0
- data/lib/abid/dsl/job_manager.rb +53 -0
- data/lib/abid/dsl/mixin.rb +52 -0
- data/lib/abid/dsl/params_spec.rb +64 -0
- data/lib/abid/dsl/play.rb +35 -0
- data/lib/abid/dsl/play_core.rb +354 -0
- data/lib/abid/dsl/rake_job.rb +41 -0
- data/lib/abid/dsl/syntax.rb +34 -0
- data/lib/abid/dsl/task.rb +53 -0
- data/lib/abid/engine.rb +36 -6
- data/lib/abid/engine/executor.rb +30 -48
- data/lib/abid/engine/process.rb +102 -68
- data/lib/abid/engine/process_manager.rb +72 -28
- data/lib/abid/engine/scheduler.rb +24 -30
- data/lib/abid/engine/waiter.rb +20 -30
- data/lib/abid/engine/worker_manager.rb +26 -60
- data/lib/abid/environment.rb +12 -27
- data/lib/abid/error.rb +2 -0
- data/lib/abid/params_format.rb +29 -12
- data/lib/abid/rake_extensions.rb +11 -3
- data/lib/abid/state_manager.rb +40 -0
- data/lib/abid/state_manager/state.rb +52 -114
- data/lib/abid/state_manager/state_service.rb +88 -0
- data/lib/abid/status.rb +63 -0
- data/lib/abid/version.rb +1 -1
- metadata +19 -32
- data/lib/Abidfile.rb +0 -1
- data/lib/abid/dsl_definition.rb +0 -29
- data/lib/abid/job.rb +0 -67
- data/lib/abid/job_manager.rb +0 -22
- data/lib/abid/mixin_task.rb +0 -29
- data/lib/abid/params_parser.rb +0 -50
- data/lib/abid/play.rb +0 -66
- data/lib/abid/play_core.rb +0 -53
- data/lib/abid/rake_extensions/task.rb +0 -41
- data/lib/abid/state_manager/database.rb +0 -40
- data/lib/abid/state_manager/state_proxy.rb +0 -65
- data/lib/abid/task.rb +0 -123
- data/lib/abid/task_manager.rb +0 -61
data/lib/abid/environment.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require 'monitor'
|
2
3
|
|
3
4
|
module Abid
|
4
5
|
class Environment
|
6
|
+
extend Forwardable
|
7
|
+
|
5
8
|
def initialize
|
6
9
|
@mon = Monitor.new
|
7
10
|
end
|
@@ -10,39 +13,21 @@ module Abid
|
|
10
13
|
@application ||= Abid::Application.new(self)
|
11
14
|
end
|
12
15
|
attr_writer :application
|
13
|
-
|
14
|
-
def options
|
15
|
-
application.options
|
16
|
-
end
|
16
|
+
def_delegators :application, :options, :logger
|
17
17
|
|
18
18
|
def config
|
19
|
-
@
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def job_manager
|
25
|
-
@mon.synchronize do
|
26
|
-
@job_manager ||= JobManager.new(self)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def process_manager
|
31
|
-
@mon.synchronize do
|
32
|
-
@process_manager ||= Engine::ProcessManager.new(self)
|
33
|
-
end
|
19
|
+
return @config if @config
|
20
|
+
@mon.synchronize { @cofig ||= Config.new }
|
34
21
|
end
|
35
22
|
|
36
|
-
def
|
37
|
-
@
|
38
|
-
|
39
|
-
end
|
23
|
+
def engine
|
24
|
+
return @engine if @engine
|
25
|
+
@mon.synchronize { @engine ||= Engine.new(self) }
|
40
26
|
end
|
41
27
|
|
42
|
-
def
|
43
|
-
@
|
44
|
-
|
45
|
-
end
|
28
|
+
def state_manager
|
29
|
+
return @state_manager if @state_manager
|
30
|
+
@mon.synchronize { @state_manager ||= StateManager.new(self) }
|
46
31
|
end
|
47
32
|
end
|
48
33
|
end
|
data/lib/abid/error.rb
CHANGED
data/lib/abid/params_format.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
1
3
|
require 'shellwords'
|
2
4
|
|
3
5
|
module Abid
|
@@ -10,6 +12,10 @@ module Abid
|
|
10
12
|
end.join(' ')
|
11
13
|
end
|
12
14
|
|
15
|
+
def self.format_with_name(name, params)
|
16
|
+
name + (params.empty? ? '' : ' ' + format(params))
|
17
|
+
end
|
18
|
+
|
13
19
|
def self.format_value(value)
|
14
20
|
case value
|
15
21
|
when Numeric, TrueClass, FalseClass
|
@@ -25,20 +31,17 @@ module Abid
|
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
28
|
-
def self.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
args.each do |arg|
|
33
|
-
m = arg.match(/^(\w+)=(.*)$/m)
|
34
|
-
if m
|
35
|
-
params.store(m[1].to_sym, parse_value(m[2]))
|
36
|
-
else
|
37
|
-
tasks << arg unless arg =~ /^-/
|
38
|
-
end
|
34
|
+
def self.collect_params(args)
|
35
|
+
args.each_with_object([{}, []]) do |arg, (params, extras)|
|
36
|
+
key, val = parse_pair(arg)
|
37
|
+
key.nil? ? extras << arg : params[key] = val
|
39
38
|
end
|
39
|
+
end
|
40
40
|
|
41
|
-
|
41
|
+
def self.parse_pair(str)
|
42
|
+
m = str.match(/^(\w+)=(.*)$/)
|
43
|
+
return unless m
|
44
|
+
[m[1].to_sym, parse_value(m[2])]
|
42
45
|
end
|
43
46
|
|
44
47
|
def self.parse_value(value)
|
@@ -53,9 +56,23 @@ module Abid
|
|
53
56
|
Date.parse(value)
|
54
57
|
when /\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}( \d{4})?\z/
|
55
58
|
Time.parse(value)
|
59
|
+
when /\A(["']).*\1\z/
|
60
|
+
value[1..-2]
|
56
61
|
else
|
57
62
|
value
|
58
63
|
end
|
59
64
|
end
|
65
|
+
|
66
|
+
SUPPORTED_TYPES = [
|
67
|
+
Numeric, TrueClass, FalseClass, Date, Time, DateTime, String
|
68
|
+
].freeze
|
69
|
+
|
70
|
+
def self.validate_params!(params)
|
71
|
+
params.values.each do |value|
|
72
|
+
valid = SUPPORTED_TYPES.any? { |t| value.is_a? t }
|
73
|
+
raise Error, "#{value.class} class is not supported" unless valid
|
74
|
+
end
|
75
|
+
params
|
76
|
+
end
|
60
77
|
end
|
61
78
|
end
|
data/lib/abid/rake_extensions.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'abid/rake_extensions/task'
|
2
|
-
|
3
1
|
# Delegate Rake.application to Abid.global.application
|
4
2
|
class << Rake
|
5
3
|
def application
|
@@ -11,4 +9,14 @@ class << Rake
|
|
11
9
|
end
|
12
10
|
end
|
13
11
|
|
14
|
-
Rake
|
12
|
+
module Rake
|
13
|
+
class Task
|
14
|
+
def bind(params = {})
|
15
|
+
Abid::DSL::RakeJob.new(self, params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def params_spec
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'abid/state_manager/state'
|
3
|
+
require 'abid/state_manager/state_service'
|
4
|
+
|
5
|
+
module Abid
|
6
|
+
class StateManager
|
7
|
+
Sequel.extension :migration
|
8
|
+
|
9
|
+
MIGRATIONS_PATH = File.expand_path('../../../migrations', __FILE__)
|
10
|
+
|
11
|
+
# Creates a new database object and checks schema version.
|
12
|
+
#
|
13
|
+
# @return [Sequel::Database] database object
|
14
|
+
# @see Sequel.connect
|
15
|
+
def self.connect(*args)
|
16
|
+
db = Sequel.connect(*args)
|
17
|
+
Sequel::Migrator.check_current(db, MIGRATIONS_PATH)
|
18
|
+
db
|
19
|
+
rescue Sequel::Migrator::NotCurrentError
|
20
|
+
raise Error, 'current schema is out of date'
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(env)
|
24
|
+
@env = env
|
25
|
+
@db = self.class.connect(@env.config.database)
|
26
|
+
@states = State.connect(@db)
|
27
|
+
end
|
28
|
+
attr_reader :db, :states
|
29
|
+
|
30
|
+
# Returns StateService object from name and params.
|
31
|
+
# @param name [String] task name
|
32
|
+
# @param params [Hash]
|
33
|
+
# @return [StateManager::StateService]
|
34
|
+
def state(name, params, dryrun: false, volatile: false)
|
35
|
+
return VolatileStateService.new(@states, name, params) if volatile
|
36
|
+
return NullStateService.new(@states, name, params) if dryrun
|
37
|
+
StateService.new(@states, name, params)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,26 +1,26 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
1
3
|
module Abid
|
2
|
-
|
3
|
-
State = Class.new(Sequel::Model)
|
4
|
+
class StateManager
|
5
|
+
State = Class.new(Sequel::Model) do # delay Sequel::Model.set_dataset
|
6
|
+
def self.connect(database)
|
7
|
+
Class.new(self.Model(database[:states]))
|
8
|
+
end
|
9
|
+
end
|
4
10
|
|
5
11
|
# O/R Mapper for `states` table.
|
6
|
-
#
|
7
|
-
# Use #init_by_job to initialize a state object.
|
8
12
|
class State
|
9
13
|
RUNNING = 1
|
10
14
|
SUCCESSED = 2
|
11
15
|
FAILED = 3
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
# @param before [Time] upper bound of start_time
|
16
|
-
# @return [Sequel::Dataset<State>] a set of states started between
|
17
|
-
# the given range.
|
18
|
-
#
|
19
|
-
# @!method self.filter_by_prefix(prefix)
|
20
|
-
# @param prefix [String] the prefix of task names
|
21
|
-
# @return [Sequel::Dataset<State>] a set of states which name starts
|
22
|
-
# with the given prefix.
|
17
|
+
@dataset = nil
|
18
|
+
|
23
19
|
dataset_module do
|
20
|
+
# @param after [Time] lower bound of start_time
|
21
|
+
# @param before [Time] upper bound of start_time
|
22
|
+
# @return [Sequel::Dataset<State>] a set of states started between
|
23
|
+
# the given range.
|
24
24
|
def filter_by_start_time(after: nil, before: nil)
|
25
25
|
dataset = self
|
26
26
|
dataset = dataset.where { start_time >= after } if after
|
@@ -28,112 +28,15 @@ module Abid
|
|
28
28
|
dataset
|
29
29
|
end
|
30
30
|
|
31
|
+
# @param prefix [String] the prefix of task names
|
32
|
+
# @return [Sequel::Dataset<State>] a set of states which name starts
|
33
|
+
# with the given prefix.
|
31
34
|
def filter_by_prefix(prefix)
|
32
35
|
return self if prefix.nil?
|
33
36
|
where { Sequel.like(:name, prefix + '%') }
|
34
37
|
end
|
35
38
|
end
|
36
39
|
|
37
|
-
# Find a state by the job.
|
38
|
-
#
|
39
|
-
# @param job [Job] job
|
40
|
-
# @return [State] state object
|
41
|
-
def self.find_by_job(job)
|
42
|
-
where(
|
43
|
-
name: job.name,
|
44
|
-
params: job.params_str,
|
45
|
-
digest: job.digest
|
46
|
-
).first
|
47
|
-
end
|
48
|
-
|
49
|
-
# Initialize a state by a job.
|
50
|
-
#
|
51
|
-
# @param job [Job] job
|
52
|
-
# @return [State] state object
|
53
|
-
def self.init_by_job(job)
|
54
|
-
new(
|
55
|
-
name: job.name,
|
56
|
-
params: job.params_str,
|
57
|
-
digest: job.digest
|
58
|
-
)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Find or initialize a state by a job.
|
62
|
-
#
|
63
|
-
# @param job [Job] job
|
64
|
-
# @return [State] state object
|
65
|
-
def self.find_or_init_by_job(job)
|
66
|
-
find_by_job(job) || init_by_job(job)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Update the state to RUNNING.
|
70
|
-
#
|
71
|
-
# @param job [Job] job
|
72
|
-
def self.start(job)
|
73
|
-
db.transaction do
|
74
|
-
state = find_or_init_by_job(job)
|
75
|
-
state.check_running!
|
76
|
-
state.state = RUNNING
|
77
|
-
state.start_time = Time.now
|
78
|
-
state.end_time = nil
|
79
|
-
state.save
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Update the state to SUCCESSED or FAILED.
|
84
|
-
#
|
85
|
-
# If error is given, the state will be FAILED.
|
86
|
-
#
|
87
|
-
# @param job [Job] job
|
88
|
-
# @param error [Error] error object
|
89
|
-
def self.finish(job, error = nil)
|
90
|
-
db.transaction do
|
91
|
-
state = find_or_init_by_job(job)
|
92
|
-
return unless state.running?
|
93
|
-
|
94
|
-
state.state = error ? FAILED : SUCCESSED
|
95
|
-
state.end_time = Time.now
|
96
|
-
state.save
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Assume the job to be successed
|
101
|
-
#
|
102
|
-
# If the force option is true, update the state to SUCCESSED even if the
|
103
|
-
# task is running.
|
104
|
-
#
|
105
|
-
# @param job [Job] job
|
106
|
-
# @param force [Boolean] force update the state
|
107
|
-
# @return [void]
|
108
|
-
def self.assume(job, force: false)
|
109
|
-
db.transaction do
|
110
|
-
state = find_or_init_by_job(job)
|
111
|
-
return state if state.successed?
|
112
|
-
state.check_running! unless force
|
113
|
-
|
114
|
-
state.state = SUCCESSED
|
115
|
-
state.start_time = Time.now
|
116
|
-
state.end_time = Time.now
|
117
|
-
state.save
|
118
|
-
state
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Delete the state.
|
123
|
-
#
|
124
|
-
# @param state_id [Integer] State ID
|
125
|
-
# @param force [Boolean] If true, delete the state even if running
|
126
|
-
# @return [void]
|
127
|
-
def self.revoke(state_id, force: false)
|
128
|
-
db.transaction do
|
129
|
-
state = self[state_id]
|
130
|
-
return false if state.nil?
|
131
|
-
state.check_running! unless force
|
132
|
-
state.delete
|
133
|
-
true
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
40
|
# check if the state is running
|
138
41
|
def check_running!
|
139
42
|
raise AlreadyRunningError, 'job already running' if running?
|
@@ -164,6 +67,41 @@ module Abid
|
|
164
67
|
return unless start_time && end_time
|
165
68
|
end_time - start_time
|
166
69
|
end
|
70
|
+
|
71
|
+
# Update the state to RUNNING.
|
72
|
+
def start
|
73
|
+
check_running!
|
74
|
+
update(state: RUNNING, start_time: Time.now, end_time: nil)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Update the state to SUCCESSED or FAILED.
|
78
|
+
# If error is given, the state will be FAILED.
|
79
|
+
def finish(error = nil)
|
80
|
+
return unless running?
|
81
|
+
update(state: error ? FAILED : SUCCESSED, end_time: Time.now)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Assume the job to be successed
|
85
|
+
#
|
86
|
+
# If the `force` option is true, update the state to SUCCESSED even if the
|
87
|
+
# task is running.
|
88
|
+
def assume(force: false)
|
89
|
+
return if successed?
|
90
|
+
check_running! unless force
|
91
|
+
time = Time.now
|
92
|
+
update(state: SUCCESSED, start_time: time, end_time: time)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Delete the state.
|
96
|
+
# @param force [Boolean] If true, delete the state even if running
|
97
|
+
def revoke(force: false)
|
98
|
+
check_running! unless force
|
99
|
+
delete
|
100
|
+
end
|
101
|
+
|
102
|
+
def params_hash
|
103
|
+
YAML.load(params)
|
104
|
+
end
|
167
105
|
end
|
168
106
|
end
|
169
107
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Abid
|
4
|
+
class StateManager
|
5
|
+
# StateService updates the job status in a transaction.
|
6
|
+
class StateService
|
7
|
+
def initialize(model, name, params)
|
8
|
+
@model = model
|
9
|
+
@name = name
|
10
|
+
@params = params
|
11
|
+
end
|
12
|
+
attr_reader :name, :params
|
13
|
+
|
14
|
+
# @return [State] state
|
15
|
+
def find
|
16
|
+
cond = { name: name, params: params_text, digest: digest }
|
17
|
+
@model.where(cond).first || @model.new(cond)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @see State#start
|
21
|
+
def start
|
22
|
+
transaction { find.start }
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Try to update the state to started unless volatile.
|
27
|
+
# @see State#start
|
28
|
+
# @return [true,false] false if already running
|
29
|
+
def try_start
|
30
|
+
start
|
31
|
+
true
|
32
|
+
rescue AlreadyRunningError
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see State#finish
|
37
|
+
def finish(error = nil)
|
38
|
+
transaction { find.finish(error) }
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def assume(force: false)
|
43
|
+
find.assume(force: force)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def transaction(&block)
|
50
|
+
@model.db.transaction(
|
51
|
+
isolation: :serializable,
|
52
|
+
retry_on: Sequel::SerializationFailure,
|
53
|
+
&block
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def params_text
|
58
|
+
@params_text ||= YAML.dump(params.sort.to_h)
|
59
|
+
end
|
60
|
+
|
61
|
+
def digest
|
62
|
+
@digest ||= Digest::MD5.hexdigest(name + "\n" + params_text)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# NullStateService does not update job status.
|
67
|
+
class NullStateService < StateService
|
68
|
+
def start
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def finish(_ = nil)
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def assume(_ = {})
|
77
|
+
self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# VolatileStateService never access to database.
|
82
|
+
class VolatileStateService < NullStateService
|
83
|
+
def find
|
84
|
+
@model.new(name: name, params: params_text, digest: digest)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|