abid 0.3.0.pre.alpha.3 → 0.3.0.pre.alpha.4
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/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
|