bricolage 5.30.0 → 6.0.0beta1
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/.gitignore +1 -3
- data/bricolage.gemspec +1 -0
- data/jobclass/streaming_load.rb +2 -2
- data/lib/bricolage/application.rb +5 -5
- data/lib/bricolage/context.rb +18 -11
- data/lib/bricolage/dao/job.rb +184 -0
- data/lib/bricolage/dao/jobexecution.rb +253 -0
- data/lib/bricolage/dao/jobnet.rb +158 -0
- data/lib/bricolage/datasource.rb +1 -1
- data/lib/bricolage/exception.rb +11 -0
- data/lib/bricolage/job.rb +10 -6
- data/lib/bricolage/jobnet.rb +9 -6
- data/lib/bricolage/jobnetrunner.rb +82 -45
- data/lib/bricolage/logger.rb +2 -2
- data/lib/bricolage/loglocator.rb +19 -1
- data/lib/bricolage/postgresconnection.rb +6 -4
- data/lib/bricolage/psqldatasource.rb +54 -4
- data/lib/bricolage/sqlutils.rb +43 -1
- data/lib/bricolage/taskqueue.rb +221 -63
- data/lib/bricolage/version.rb +1 -1
- data/schema/Dockerfile +13 -0
- data/schema/Gemfile +4 -0
- data/schema/Gemfile.lock +37 -0
- data/schema/Schemafile +57 -0
- data/schema/database.yml +8 -0
- data/schema/ridgepole_dryrun.sh +2 -0
- data/schema/ridgepole_merge.sh +2 -0
- metadata +29 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 111b4bb62234f5a428ccd1da5a5ee2d6d4b1a051c8fda9ddd6fac3e590356797
|
|
4
|
+
data.tar.gz: 77c3857f92a721503bd07f6e235c81f454d61e3a55e111e7a2c0ce72efa8f6ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca02be7fe28f3323f8dcfa1f9c88a0dd70050f7af75109ee077d0347a5f7a4420bb7f7a6b80d9ad207bd447b640089b979e319fa9ed3cf3465e11039327bc2f3
|
|
7
|
+
data.tar.gz: 7d9d77d27f31d5368a694b9514a1276532335e1f980b2dd45fa77b7a4f30cc500b4dc1fb6a99a0a2f5dd35b42c9e155201764ba3d1d9a253b52790acf1f92857
|
data/.gitignore
CHANGED
data/bricolage.gemspec
CHANGED
data/jobclass/streaming_load.rb
CHANGED
|
@@ -212,7 +212,7 @@ class StreamingLoadJobClass < RubyJobClass
|
|
|
212
212
|
end
|
|
213
213
|
@logger.info "creating manifest: #{manifest_name}"
|
|
214
214
|
json = make_manifest_json(objects)
|
|
215
|
-
@logger.
|
|
215
|
+
@logger.debug "manifest:\n" + json
|
|
216
216
|
url = @src.put_control_file(manifest_name, json, noop: @noop)
|
|
217
217
|
yield url
|
|
218
218
|
@src.remove_control_file(File.basename(url), noop: @noop) unless @keep_ctl
|
|
@@ -241,7 +241,7 @@ class StreamingLoadJobClass < RubyJobClass
|
|
|
241
241
|
log_name = "load_log-#{@job_process_id}.csv"
|
|
242
242
|
@logger.info "creating tmp load log: #{log_name}"
|
|
243
243
|
csv = make_load_log_csv(objects)
|
|
244
|
-
@logger.
|
|
244
|
+
@logger.debug "load_log:\n" + csv
|
|
245
245
|
url = @src.put_control_file(log_name, csv, noop: @noop)
|
|
246
246
|
begin
|
|
247
247
|
yield url
|
|
@@ -39,7 +39,7 @@ module Bricolage
|
|
|
39
39
|
@hooks.run_before_option_parsing_hooks(opts)
|
|
40
40
|
opts.parse!(ARGV)
|
|
41
41
|
|
|
42
|
-
@ctx = Context.for_application(opts.home, opts.job_file, environment: opts.environment,
|
|
42
|
+
@ctx = Context.for_application(opts.home, opts.job_file, environment: opts.environment, option_variables: opts.option_variables)
|
|
43
43
|
opts.merge_saved_options(@ctx.load_system_options)
|
|
44
44
|
|
|
45
45
|
if opts.dump_options?
|
|
@@ -294,7 +294,7 @@ module Bricolage
|
|
|
294
294
|
@job_file = nil
|
|
295
295
|
@environment = nil
|
|
296
296
|
@home = nil
|
|
297
|
-
@
|
|
297
|
+
@option_variables = Variables.new
|
|
298
298
|
@dry_run = false
|
|
299
299
|
@explain = false
|
|
300
300
|
@list_global_variables = false
|
|
@@ -351,9 +351,9 @@ Global Options:
|
|
|
351
351
|
parser.on('-r', '--require=FEATURE', 'Requires ruby library.') {|feature|
|
|
352
352
|
require feature
|
|
353
353
|
}
|
|
354
|
-
parser.on('-v', '--variable=NAME=VALUE', 'Set
|
|
354
|
+
parser.on('-v', '--variable=NAME=VALUE', 'Set option variable.') {|name_value|
|
|
355
355
|
name, value = name_value.split('=', 2)
|
|
356
|
-
@
|
|
356
|
+
@option_variables[name] = value
|
|
357
357
|
}
|
|
358
358
|
parser.on('--dump-options', 'Shows option parsing result and quit.') {
|
|
359
359
|
@dump_options = true
|
|
@@ -401,7 +401,7 @@ Global Options:
|
|
|
401
401
|
@dump_options
|
|
402
402
|
end
|
|
403
403
|
|
|
404
|
-
attr_reader :
|
|
404
|
+
attr_reader :option_variables
|
|
405
405
|
|
|
406
406
|
def list_global_variables?
|
|
407
407
|
@list_global_variables
|
data/lib/bricolage/context.rb
CHANGED
|
@@ -19,7 +19,7 @@ module Bricolage
|
|
|
19
19
|
FileSystem.home_path(opt_path)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def Context.for_application(home_path = nil, job_path_0 = nil, job_path: nil, environment: nil,
|
|
22
|
+
def Context.for_application(home_path = nil, job_path_0 = nil, job_path: nil, environment: nil, option_variables: nil, logger: nil)
|
|
23
23
|
env = environment(environment)
|
|
24
24
|
if (job_path ||= job_path_0)
|
|
25
25
|
fs = FileSystem.for_job_path(job_path, env)
|
|
@@ -29,24 +29,28 @@ module Bricolage
|
|
|
29
29
|
else
|
|
30
30
|
fs = FileSystem.for_options(home_path, env)
|
|
31
31
|
end
|
|
32
|
-
load(fs, env,
|
|
32
|
+
load(fs, env, option_variables: option_variables, logger: logger)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def Context.load(fs, env,
|
|
36
|
-
new(fs, env,
|
|
35
|
+
def Context.load(fs, env, option_variables: nil, data_sources: nil, logger: nil)
|
|
36
|
+
new(fs, env, option_variables: option_variables, logger: logger).tap {|ctx|
|
|
37
37
|
ctx.load_configurations
|
|
38
38
|
}
|
|
39
39
|
end
|
|
40
40
|
private_class_method :load
|
|
41
41
|
|
|
42
|
-
def initialize(fs, env,
|
|
43
|
-
@logger = logger ||
|
|
42
|
+
def initialize(fs, env, option_variables: nil, data_sources: nil, logger: nil)
|
|
43
|
+
@logger = logger || default_logger(env)
|
|
44
44
|
@filesystem = fs
|
|
45
45
|
@environment = env
|
|
46
|
-
@
|
|
46
|
+
@option_variables = option_variables || Variables.new
|
|
47
47
|
@data_sources = data_sources
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
private def default_logger(env)
|
|
51
|
+
Logger.new(device: $stderr, level: Logger::INFO)
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
def load_configurations
|
|
51
55
|
@filesystem.config_pathes('prelude.rb').each do |path|
|
|
52
56
|
EmbeddedCodeAPI.module_eval(File.read(path), path.to_s, 1) if path.exist?
|
|
@@ -56,6 +60,7 @@ module Bricolage
|
|
|
56
60
|
|
|
57
61
|
attr_reader :environment
|
|
58
62
|
attr_reader :logger
|
|
63
|
+
attr_reader :option_variables
|
|
59
64
|
|
|
60
65
|
def get_data_source(type, name)
|
|
61
66
|
@data_sources.get(type, name)
|
|
@@ -63,7 +68,7 @@ module Bricolage
|
|
|
63
68
|
|
|
64
69
|
def subsystem(id)
|
|
65
70
|
self.class.new(@filesystem.subsystem(id), @environment,
|
|
66
|
-
|
|
71
|
+
option_variables: @option_variables,
|
|
67
72
|
data_sources: @data_sources,
|
|
68
73
|
logger: @logger)
|
|
69
74
|
end
|
|
@@ -102,7 +107,6 @@ module Bricolage
|
|
|
102
107
|
Variables.union(
|
|
103
108
|
builtin_variables,
|
|
104
109
|
load_global_variables,
|
|
105
|
-
@opt_global_variables
|
|
106
110
|
)
|
|
107
111
|
end
|
|
108
112
|
|
|
@@ -130,8 +134,11 @@ module Bricolage
|
|
|
130
134
|
|
|
131
135
|
def load_variables(path)
|
|
132
136
|
Variables.define {|vars|
|
|
133
|
-
@filesystem.config_file_loader.load_yaml(path)
|
|
134
|
-
|
|
137
|
+
kvs = @filesystem.config_file_loader.load_yaml(path)
|
|
138
|
+
if kvs
|
|
139
|
+
kvs.each do |name, value|
|
|
140
|
+
vars[name] = value
|
|
141
|
+
end
|
|
135
142
|
end
|
|
136
143
|
}
|
|
137
144
|
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
require 'bricolage/exception'
|
|
2
|
+
|
|
3
|
+
module Bricolage
|
|
4
|
+
module DAO
|
|
5
|
+
|
|
6
|
+
class Job
|
|
7
|
+
include SQLUtils
|
|
8
|
+
|
|
9
|
+
Attributes = Struct.new(:id, :subsystem, :job_name, :jobnet_id, :executor_id, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
def Job.for_record(r)
|
|
12
|
+
Attributes.new(
|
|
13
|
+
id: r['job_id']&.to_i,
|
|
14
|
+
subsystem: r['subsystem'],
|
|
15
|
+
job_name: r['job_name'],
|
|
16
|
+
jobnet_id: r['jobnet_id']&.to_i,
|
|
17
|
+
executor_id: r['executor_id']
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(datasource)
|
|
22
|
+
@datasource = datasource
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private def connect(&block)
|
|
26
|
+
@datasource.open_shared_connection(&block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_or_create(jobnet_id, job_ref)
|
|
30
|
+
connect {|conn|
|
|
31
|
+
job = find(conn, jobnet_id, job_ref) # optimize the most frequent case
|
|
32
|
+
if job
|
|
33
|
+
job
|
|
34
|
+
else
|
|
35
|
+
begin
|
|
36
|
+
create(conn, jobnet_id, job_ref)
|
|
37
|
+
rescue UniqueViolationException
|
|
38
|
+
find(conn, jobnet_id, job_ref) or raise "[BUG] Could not find/create job record: jobnet_id=#{jobnet_id}, ref=#{job_ref}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private def find(conn, jobnet_id, job_ref)
|
|
45
|
+
record = conn.query_row(<<~EndSQL)
|
|
46
|
+
select
|
|
47
|
+
"job_id"
|
|
48
|
+
, "subsystem"
|
|
49
|
+
, "job_name"
|
|
50
|
+
, "executor_id"
|
|
51
|
+
, jobnet_id
|
|
52
|
+
from
|
|
53
|
+
jobs
|
|
54
|
+
where
|
|
55
|
+
jobnet_id = #{jobnet_id}
|
|
56
|
+
and "subsystem" = #{s job_ref.subsystem}
|
|
57
|
+
and "job_name" = #{s job_ref.name}
|
|
58
|
+
;
|
|
59
|
+
EndSQL
|
|
60
|
+
|
|
61
|
+
if record
|
|
62
|
+
Job.for_record(record)
|
|
63
|
+
else
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private def create(conn, jobnet_id, job_ref)
|
|
69
|
+
record = conn.query_row(<<~EndSQL)
|
|
70
|
+
insert into jobs
|
|
71
|
+
( "subsystem"
|
|
72
|
+
, "job_name"
|
|
73
|
+
, jobnet_id
|
|
74
|
+
)
|
|
75
|
+
values
|
|
76
|
+
( #{s job_ref.subsystem}
|
|
77
|
+
, #{s job_ref.name}
|
|
78
|
+
, #{jobnet_id}
|
|
79
|
+
)
|
|
80
|
+
returning "job_id", "subsystem", "job_name", jobnet_id
|
|
81
|
+
;
|
|
82
|
+
EndSQL
|
|
83
|
+
|
|
84
|
+
Job.for_record(record)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def locked?(job_ids)
|
|
88
|
+
count = connect {|conn|
|
|
89
|
+
conn.query_value(<<~EndSQL)
|
|
90
|
+
select
|
|
91
|
+
count(job_id)
|
|
92
|
+
from
|
|
93
|
+
jobs
|
|
94
|
+
where
|
|
95
|
+
job_id in (#{job_ids.join(',')})
|
|
96
|
+
and executor_id is not null
|
|
97
|
+
;
|
|
98
|
+
EndSQL
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
count.to_i > 0
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def locked_jobs(jobnet_id)
|
|
105
|
+
records = connect {|conn|
|
|
106
|
+
conn.query_rows(<<~EndSQL)
|
|
107
|
+
select
|
|
108
|
+
"job_id"
|
|
109
|
+
, "subsystem"
|
|
110
|
+
, "job_name"
|
|
111
|
+
, jobnet_id
|
|
112
|
+
, "executor_id"
|
|
113
|
+
from
|
|
114
|
+
jobs
|
|
115
|
+
where
|
|
116
|
+
jobnet_id = #{jobnet_id}
|
|
117
|
+
and executor_id is not null
|
|
118
|
+
;
|
|
119
|
+
EndSQL
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if records.empty?
|
|
123
|
+
[]
|
|
124
|
+
else
|
|
125
|
+
record.map {|r| Job.for_record(r) }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def lock(job_id, executor_id)
|
|
130
|
+
records = connect {|conn|
|
|
131
|
+
conn.execute_update(<<~EndSQL)
|
|
132
|
+
update jobs
|
|
133
|
+
set
|
|
134
|
+
executor_id = #{s executor_id}
|
|
135
|
+
where
|
|
136
|
+
job_id = #{job_id}
|
|
137
|
+
and executor_id is null
|
|
138
|
+
returning job_id
|
|
139
|
+
;
|
|
140
|
+
EndSQL
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if records.empty?
|
|
144
|
+
raise DoubleLockError, "Could not lock job: job_id=#{job_id}"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Unlock the job.
|
|
149
|
+
# Returns true if successfully unlocked, otherwise false.
|
|
150
|
+
# FIXME: raise an exception on failing unlock?
|
|
151
|
+
def unlock(job_id, executor_id)
|
|
152
|
+
records = connect {|conn|
|
|
153
|
+
conn.execute_update(<<~EndSQL)
|
|
154
|
+
update jobs
|
|
155
|
+
set
|
|
156
|
+
executor_id = null
|
|
157
|
+
where
|
|
158
|
+
job_id = #{job_id}
|
|
159
|
+
and executor_id = #{s executor_id}
|
|
160
|
+
returning job_id
|
|
161
|
+
;
|
|
162
|
+
EndSQL
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
not records.empty?
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def clear_lock_all(jobnet_id)
|
|
169
|
+
connect {|conn|
|
|
170
|
+
conn.execute_update(<<~EndSQL)
|
|
171
|
+
update jobs
|
|
172
|
+
set
|
|
173
|
+
executor_id = null
|
|
174
|
+
where
|
|
175
|
+
jobnet_id = #{jobnet_id}
|
|
176
|
+
;
|
|
177
|
+
EndSQL
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
end # class Job
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
module Bricolage
|
|
2
|
+
module DAO
|
|
3
|
+
|
|
4
|
+
class JobExecution
|
|
5
|
+
include SQLUtils
|
|
6
|
+
|
|
7
|
+
STATUS_WAITING = 'waiting'.freeze
|
|
8
|
+
STATUS_SUCCEEDED = 'succeeded'.freeze
|
|
9
|
+
STATUS_RUNNING = 'running'.freeze
|
|
10
|
+
STATUS_FAILED = 'failed'.freeze
|
|
11
|
+
STATUS_CANCELED = 'canceled'.freeze
|
|
12
|
+
|
|
13
|
+
Attributes = Struct.new(:job_id, :job_execution_id, :subsystem, :job_name, keyword_init: true)
|
|
14
|
+
|
|
15
|
+
def JobExecution.for_record(r)
|
|
16
|
+
Attributes.new(
|
|
17
|
+
job_id: r['job_id']&.to_i,
|
|
18
|
+
job_execution_id: r['job_execution_id']&.to_i,
|
|
19
|
+
subsystem: r['subsystem'],
|
|
20
|
+
job_name: r['job_name']
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def JobExecution.for_connection(conn)
|
|
25
|
+
new(nil, connection: conn)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(datasource, connection: nil)
|
|
29
|
+
@datasource = datasource
|
|
30
|
+
@connection = connection
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private def connect
|
|
34
|
+
if @connection
|
|
35
|
+
yield @connection
|
|
36
|
+
else
|
|
37
|
+
@datasource.open_shared_connection {|conn|
|
|
38
|
+
yield conn
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def enqueued_jobs(jobnet_ref)
|
|
44
|
+
records = connect {|conn|
|
|
45
|
+
conn.query_rows(<<~EndSQL)
|
|
46
|
+
select
|
|
47
|
+
e.job_execution_id
|
|
48
|
+
, e.job_id
|
|
49
|
+
, j.subsystem
|
|
50
|
+
, j.job_name
|
|
51
|
+
from
|
|
52
|
+
job_executions e
|
|
53
|
+
inner join jobs j using (job_id)
|
|
54
|
+
inner join jobnets n using (jobnet_id)
|
|
55
|
+
where
|
|
56
|
+
n.subsystem = #{s jobnet_ref.subsystem}
|
|
57
|
+
and n.jobnet_name = #{s jobnet_ref.name}
|
|
58
|
+
and e.status in (#{s STATUS_WAITING}, #{s STATUS_RUNNING}, #{s STATUS_FAILED})
|
|
59
|
+
order by
|
|
60
|
+
e.execution_sequence
|
|
61
|
+
;
|
|
62
|
+
EndSQL
|
|
63
|
+
}
|
|
64
|
+
records.map {|r| JobExecution.for_record(r) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def enqueue_job(job, execution_sequence)
|
|
68
|
+
record = nil
|
|
69
|
+
connect {|conn|
|
|
70
|
+
records = conn.execute_update(<<~EndSQL)
|
|
71
|
+
insert into job_executions
|
|
72
|
+
( job_id
|
|
73
|
+
, execution_sequence
|
|
74
|
+
, status
|
|
75
|
+
, message
|
|
76
|
+
, submitted_at
|
|
77
|
+
)
|
|
78
|
+
values
|
|
79
|
+
( #{job.id}
|
|
80
|
+
, #{execution_sequence}
|
|
81
|
+
, #{s STATUS_WAITING}
|
|
82
|
+
, ''
|
|
83
|
+
, now()
|
|
84
|
+
)
|
|
85
|
+
returning job_execution_id, job_id
|
|
86
|
+
;
|
|
87
|
+
EndSQL
|
|
88
|
+
|
|
89
|
+
record = records.first
|
|
90
|
+
save_state_transition(conn, record['job_execution_id'], 'submitted_at')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exec = JobExecution.for_record(record)
|
|
94
|
+
exec.subsystem = job.subsystem
|
|
95
|
+
exec.job_name = job.job_name
|
|
96
|
+
exec
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def cancel_jobnet(jobnet_ref, message)
|
|
100
|
+
connect {|conn|
|
|
101
|
+
records = conn.execute_update(<<~EndSQL)
|
|
102
|
+
update job_executions
|
|
103
|
+
set
|
|
104
|
+
status = #{s STATUS_CANCELED}
|
|
105
|
+
, message = #{s message}
|
|
106
|
+
, finished_at = now()
|
|
107
|
+
where
|
|
108
|
+
job_id in (
|
|
109
|
+
select
|
|
110
|
+
j.job_id
|
|
111
|
+
from
|
|
112
|
+
jobs j inner join jobnets n using (jobnet_id)
|
|
113
|
+
where
|
|
114
|
+
n.subsystem = #{s jobnet_ref.subsystem}
|
|
115
|
+
and n.jobnet_name = #{s jobnet_ref.name}
|
|
116
|
+
)
|
|
117
|
+
and status in (#{s STATUS_WAITING}, #{s STATUS_RUNNING}, #{s STATUS_FAILED})
|
|
118
|
+
returning job_execution_id
|
|
119
|
+
;
|
|
120
|
+
EndSQL
|
|
121
|
+
|
|
122
|
+
job_execution_ids = records.map {|r| r['job_execution_id'].to_i }
|
|
123
|
+
unless job_execution_ids.empty?
|
|
124
|
+
conn.execute_update(<<~EndSQL)
|
|
125
|
+
insert into job_execution_states
|
|
126
|
+
( job_execution_id
|
|
127
|
+
, job_id
|
|
128
|
+
, created_at
|
|
129
|
+
, status
|
|
130
|
+
, message
|
|
131
|
+
)
|
|
132
|
+
select
|
|
133
|
+
job_execution_id
|
|
134
|
+
, job_id
|
|
135
|
+
, finished_at
|
|
136
|
+
, status
|
|
137
|
+
, message
|
|
138
|
+
from
|
|
139
|
+
job_executions
|
|
140
|
+
where
|
|
141
|
+
job_execution_id in (#{job_execution_ids.join(', ')})
|
|
142
|
+
;
|
|
143
|
+
EndSQL
|
|
144
|
+
end
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def transition_to_running(job_execution_id)
|
|
149
|
+
connect {|conn|
|
|
150
|
+
records = conn.execute_update(<<~EndSQL)
|
|
151
|
+
update job_executions
|
|
152
|
+
set
|
|
153
|
+
status = #{s STATUS_RUNNING}
|
|
154
|
+
, message = ''
|
|
155
|
+
, started_at = now()
|
|
156
|
+
, finished_at = null
|
|
157
|
+
where
|
|
158
|
+
job_execution_id = #{job_execution_id}
|
|
159
|
+
and status in (#{s STATUS_WAITING}, #{s STATUS_FAILED})
|
|
160
|
+
returning job_execution_id
|
|
161
|
+
;
|
|
162
|
+
EndSQL
|
|
163
|
+
if records.empty?
|
|
164
|
+
raise IllegalJobStateException, "Could not run already running job: job_execution_id=#{job_execution_id}"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
save_state_transition(conn, job_execution_id, 'started_at')
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def transition_to_succeeded(job_execution_id)
|
|
172
|
+
connect {|conn|
|
|
173
|
+
records = conn.execute_update(<<~EndSQL)
|
|
174
|
+
update job_executions
|
|
175
|
+
set
|
|
176
|
+
finished_at = now()
|
|
177
|
+
, status = #{s STATUS_SUCCEEDED}
|
|
178
|
+
, message = ''
|
|
179
|
+
where
|
|
180
|
+
job_execution_id = #{job_execution_id}
|
|
181
|
+
and status = #{s STATUS_RUNNING}
|
|
182
|
+
returning job_execution_id
|
|
183
|
+
;
|
|
184
|
+
EndSQL
|
|
185
|
+
if records.empty?
|
|
186
|
+
raise IllegalJobStateException, "could not transition to succeeded state: job_execution_id=#{job_execution_id}"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
save_state_transition(conn, job_execution_id, 'finished_at')
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def transition_to_failed(job_execution_id, message)
|
|
194
|
+
connect {|conn|
|
|
195
|
+
records = conn.execute_update(<<~EndSQL)
|
|
196
|
+
update job_executions
|
|
197
|
+
set
|
|
198
|
+
finished_at = now()
|
|
199
|
+
, status = #{s STATUS_FAILED}
|
|
200
|
+
, message = #{s message}
|
|
201
|
+
where
|
|
202
|
+
job_execution_id = #{job_execution_id}
|
|
203
|
+
and status = #{s STATUS_RUNNING}
|
|
204
|
+
returning job_execution_id
|
|
205
|
+
;
|
|
206
|
+
EndSQL
|
|
207
|
+
if records.empty?
|
|
208
|
+
raise IllegalJobStateException, "could not transition to failed state: job_execution_id=#{job_execution_id}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
save_state_transition(conn, job_execution_id, 'finished_at')
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private def save_state_transition(conn, job_execution_id, time_expr)
|
|
216
|
+
conn.execute_update(<<~EndSQL)
|
|
217
|
+
insert into job_execution_states
|
|
218
|
+
( job_execution_id
|
|
219
|
+
, job_id
|
|
220
|
+
, created_at
|
|
221
|
+
, status
|
|
222
|
+
, message
|
|
223
|
+
)
|
|
224
|
+
select
|
|
225
|
+
job_execution_id
|
|
226
|
+
, job_id
|
|
227
|
+
, #{time_expr}
|
|
228
|
+
, status
|
|
229
|
+
, message
|
|
230
|
+
from
|
|
231
|
+
job_executions
|
|
232
|
+
where
|
|
233
|
+
job_execution_id = #{job_execution_id}
|
|
234
|
+
;
|
|
235
|
+
EndSQL
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# For tests only
|
|
239
|
+
def delete_all
|
|
240
|
+
connect {|conn|
|
|
241
|
+
conn.execute_update(<<~EndSQL)
|
|
242
|
+
delete from job_execution_states;
|
|
243
|
+
delete from job_executions;
|
|
244
|
+
delete from jobs;
|
|
245
|
+
delete from jobnets;
|
|
246
|
+
EndSQL
|
|
247
|
+
}
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
end # class JobExecution
|
|
251
|
+
|
|
252
|
+
end
|
|
253
|
+
end
|