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.
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