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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/abid.gemspec +1 -2
  4. data/exe/abidsc +1 -1
  5. data/lib/abid.rb +3 -30
  6. data/lib/abid/application.rb +83 -13
  7. data/lib/abid/cli/assume.rb +7 -8
  8. data/lib/abid/cli/list.rb +2 -2
  9. data/lib/abid/cli/migrate.rb +1 -1
  10. data/lib/abid/cli/revoke.rb +9 -10
  11. data/lib/abid/config.rb +2 -0
  12. data/lib/abid/dsl/abid_job.rb +58 -0
  13. data/lib/abid/dsl/actions.rb +36 -0
  14. data/lib/abid/dsl/job.rb +58 -0
  15. data/lib/abid/dsl/job_manager.rb +53 -0
  16. data/lib/abid/dsl/mixin.rb +52 -0
  17. data/lib/abid/dsl/params_spec.rb +64 -0
  18. data/lib/abid/dsl/play.rb +35 -0
  19. data/lib/abid/dsl/play_core.rb +354 -0
  20. data/lib/abid/dsl/rake_job.rb +41 -0
  21. data/lib/abid/dsl/syntax.rb +34 -0
  22. data/lib/abid/dsl/task.rb +53 -0
  23. data/lib/abid/engine.rb +36 -6
  24. data/lib/abid/engine/executor.rb +30 -48
  25. data/lib/abid/engine/process.rb +102 -68
  26. data/lib/abid/engine/process_manager.rb +72 -28
  27. data/lib/abid/engine/scheduler.rb +24 -30
  28. data/lib/abid/engine/waiter.rb +20 -30
  29. data/lib/abid/engine/worker_manager.rb +26 -60
  30. data/lib/abid/environment.rb +12 -27
  31. data/lib/abid/error.rb +2 -0
  32. data/lib/abid/params_format.rb +29 -12
  33. data/lib/abid/rake_extensions.rb +11 -3
  34. data/lib/abid/state_manager.rb +40 -0
  35. data/lib/abid/state_manager/state.rb +52 -114
  36. data/lib/abid/state_manager/state_service.rb +88 -0
  37. data/lib/abid/status.rb +63 -0
  38. data/lib/abid/version.rb +1 -1
  39. metadata +19 -32
  40. data/lib/Abidfile.rb +0 -1
  41. data/lib/abid/dsl_definition.rb +0 -29
  42. data/lib/abid/job.rb +0 -67
  43. data/lib/abid/job_manager.rb +0 -22
  44. data/lib/abid/mixin_task.rb +0 -29
  45. data/lib/abid/params_parser.rb +0 -50
  46. data/lib/abid/play.rb +0 -66
  47. data/lib/abid/play_core.rb +0 -53
  48. data/lib/abid/rake_extensions/task.rb +0 -41
  49. data/lib/abid/state_manager/database.rb +0 -40
  50. data/lib/abid/state_manager/state_proxy.rb +0 -65
  51. data/lib/abid/task.rb +0 -123
  52. data/lib/abid/task_manager.rb +0 -61
@@ -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
- @mon.synchronize do
20
- @cofig ||= Config.new
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 worker_manager
37
- @mon.synchronize do
38
- @worker_manager ||= Engine::WorkerManager.new(self)
39
- end
23
+ def engine
24
+ return @engine if @engine
25
+ @mon.synchronize { @engine ||= Engine.new(self) }
40
26
  end
41
27
 
42
- def db
43
- @mon.synchronize do
44
- @db ||= StateManager::Database.new(self)
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
@@ -2,4 +2,6 @@ module Abid
2
2
  class Error < StandardError; end
3
3
 
4
4
  class AlreadyRunningError < Error; end
5
+
6
+ class NoParamError < NameError; end
5
7
  end
@@ -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.parse_args(args)
29
- tasks = []
30
- params = {}
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
- [tasks, params]
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
@@ -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::Task.include Abid::RakeExtensions::Task
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
- module StateManager
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
- # @!method self.filter_by_start_time(after: nil, before: nil)
14
- # @param after [Time] lower bound of start_time
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